from __future__ import annotations

import inspect
from collections import defaultdict
from typing import Any, Callable


class HookRegistry:
    """
    Async-aware event/hook registry for repository lifecycle events.

    Events:
        before_create / after_create
        before_update / after_update
        before_save   / after_save
        before_delete / after_delete
        before_find   / after_find

    Usage::

        registry = HookRegistry()
        registry.on("before_create", my_handler)
        await registry.emit("before_create", data)
    """

    def on(self, event: str, handler: Callable) -> None:
        """Register a handler for event."""
        self._handlers[event].append(handler)

    def off(self, event: str, handler: Callable) -> None:
        """Unregister a handler."""
        self._handlers[event] = [
            h for h in self._handlers[event] if h is not handler
        ]

    def clear(self, event: str | None = None) -> None:
        """Remove all handlers for event, or all events if event is None."""
        if event is None:
            self._handlers.clear()
        else:
            self._handlers.pop(event, None)

    async def emit(self, event: str, *args: Any, **kwargs: Any) -> None:
        """Call every registered handler for event in registration order."""
        for handler in list(self._handlers.get(event, [])):
            if inspect.iscoroutinefunction(handler):
                await handler(*args, **kwargs)
            else:
                handler(*args, **kwargs)

    def __init__(self) -> None:
        self._handlers: dict[str, list[Callable]] = defaultdict(list)


def hook(*events: str) -> Callable:
    """
    Class-method decorator that marks a method as a hook handler.

    Methods decorated with this are automatically registered when
    BaseRepository is instantiated::

        class ArticleRepository(BaseRepository[Article]):
            @hook("before_create")
            async def validate_title(self, data: dict) -> None:
                if not data.get("title"):
                    raise ValueError("title is required")

            @hook("after_create", "after_update")
            async def sync_search(self, record: dict) -> None:
                await self.search.insert_one("articles", record)
    """

    def decorator(fn: Callable) -> Callable:
        if not hasattr(fn, "_hook_events"):
            fn._hook_events: list[str] = []
        fn._hook_events.extend(events)
        return fn

    return decorator
