"""Hook system for tool execution lifecycle events.

Hooks allow observing and reacting to tool calls without modifying the
tools themselves.  They run at the ``ToolRegistry.execute()`` level so
every tool call — whether from the main agent loop or a subagent — fires
the same hooks.

Usage::

    from flowly.agent.hooks import HookRegistry, HookContext

    hooks = HookRegistry()

    @hooks.on_post_tool
    async def track_tool_usage(ctx: HookContext):
        print(f"{ctx.tool_name} took {ctx.duration_ms:.0f}ms success={ctx.success}")

    registry = ToolRegistry(hooks=hooks)
"""

from __future__ import annotations

import logging
import time
from dataclasses import dataclass, field
from typing import Any, Awaitable, Callable

logger = logging.getLogger(__name__)

# A hook function: sync or async, receives HookContext, returns nothing.
HookFn = Callable[["HookContext"], Awaitable[None] | None]


@dataclass
class HookContext:
    """Context passed to hook callbacks."""

    tool_name: str
    params: dict[str, Any]
    # Populated only in post-tool hooks:
    result: str | None = None
    duration_ms: float = 0.0
    success: bool | None = None


class HookRegistry:
    """Registry for pre/post tool-call hooks."""

    def __init__(self) -> None:
        self._pre_tool: list[HookFn] = []
        self._post_tool: list[HookFn] = []

    # ── Registration (usable as decorator or plain call) ────────────

    def on_pre_tool(self, fn: HookFn) -> HookFn:
        """Register a pre-tool-call hook.  Returns *fn* unchanged."""
        self._pre_tool.append(fn)
        return fn

    def on_post_tool(self, fn: HookFn) -> HookFn:
        """Register a post-tool-call hook.  Returns *fn* unchanged."""
        self._post_tool.append(fn)
        return fn

    # ── Firing ──────────────────────────────────────────────────────

    async def fire_pre_tool(self, ctx: HookContext) -> None:
        """Invoke all pre-tool hooks.  Errors are logged, never raised."""
        for fn in self._pre_tool:
            try:
                ret = fn(ctx)
                if ret is not None and hasattr(ret, "__await__"):
                    await ret
            except Exception:
                logger.debug("pre_tool hook error for %s", ctx.tool_name, exc_info=True)

    async def fire_post_tool(self, ctx: HookContext) -> None:
        """Invoke all post-tool hooks.  Errors are logged, never raised."""
        for fn in self._post_tool:
            try:
                ret = fn(ctx)
                if ret is not None and hasattr(ret, "__await__"):
                    await ret
            except Exception:
                logger.debug("post_tool hook error for %s", ctx.tool_name, exc_info=True)
