"""Lifecycle hooks — synchronous, in-process extension points.

Hooks are intentionally **distinct from events**:

* Events are asynchronous notifications: "this happened, react if you care".
* Hooks are synchronous extension points: "I'm about to do X — does anyone
  want to veto, mutate the input, or contribute extra work to the same
  transaction?".

Use hooks for: validation pipelines, computed-field injection, policy
extensions. Use events for: notifications, audit, denormalised cache busts.
"""

from __future__ import annotations

from collections import defaultdict
from collections.abc import Callable
from typing import Any

import structlog

_log = structlog.get_logger("simorgh.hooks")


class HookError(Exception):
    """Raised when a hook handler vetoes the operation."""


# name -> list of (priority, callable). Lower priority runs first.
_HOOKS: dict[str, list[tuple[int, Callable[..., Any]]]] = defaultdict(list)


def register_hook(name: str, *, priority: int = 100):
    """Decorator: register `fn` as a handler for hook `name`."""

    def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
        _HOOKS[name].append((priority, fn))
        _HOOKS[name].sort(key=lambda t: t[0])
        return fn

    return decorator


def run_hook(name: str, /, *args, **kwargs) -> list[Any]:
    """Invoke every handler for `name` in priority order. Returns their results.

    A handler may raise :class:`HookError` to veto — the exception propagates
    and the operation must be aborted by the caller.
    """
    results: list[Any] = []
    for _prio, fn in _HOOKS.get(name, ()):
        try:
            results.append(fn(*args, **kwargs))
        except HookError:
            raise
        except Exception as exc:
            _log.error("hook.handler_error", hook=name, error=str(exc), exc_info=True)
            raise
    return results


def clear_hooks(name: str | None = None) -> None:
    if name is None:
        _HOOKS.clear()
    else:
        _HOOKS.pop(name, None)


def registered_hooks() -> dict[str, int]:
    """Return ``{hook_name: handler_count}`` (debug / introspection)."""
    return {name: len(handlers) for name, handlers in _HOOKS.items()}


__all__ = [
    "HookError",
    "clear_hooks",
    "register_hook",
    "registered_hooks",
    "run_hook",
]
