"""Tool registry — wraps actions or arbitrary callables for agent use."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

from simorgh.apps.ai.actions import ActionSpec, get_action


class ToolError(ValueError):
    """Raised on registration or invocation errors."""


@dataclass(frozen=True)
class ToolSpec:
    name: str
    description: str
    handler: Callable[..., Any]
    action_key: str = ""   # populated when the tool wraps a registered action

    def __post_init__(self) -> None:
        if not self.name.isidentifier():
            raise ToolError(f"invalid tool name {self.name!r}")
        if not callable(self.handler):
            raise ToolError(f"tool {self.name!r}: handler is not callable")


_TOOLS: dict[str, ToolSpec] = {}


def register_tool(tool: ToolSpec) -> ToolSpec:
    existing = _TOOLS.get(tool.name)
    if existing is not None and existing != tool:
        raise ToolError(f"tool {tool.name!r} already registered with different config")
    _TOOLS[tool.name] = tool
    return tool


def tool_for_action(spec: ActionSpec) -> ToolSpec:
    """Expose a registered action as a tool with the same key as its name."""
    name = spec.key.replace(".", "_")

    def _invoke(**payload: Any) -> Any:
        from simorgh.apps.ai.actions import validate_payload

        cleaned = validate_payload(spec, payload)
        return spec.handler(**cleaned)

    tool = ToolSpec(
        name=name,
        description=spec.description or spec.label_key,
        handler=_invoke,
        action_key=spec.key,
    )
    return register_tool(tool)


def get_tool(name: str) -> ToolSpec:
    try:
        return _TOOLS[name]
    except KeyError as exc:
        raise ToolError(f"unknown tool {name!r}") from exc


def list_tools() -> list[ToolSpec]:
    return sorted(_TOOLS.values(), key=lambda t: t.name)


def invoke_tool(name: str, **payload: Any) -> Any:
    spec = get_tool(name)
    return spec.handler(**payload)


def reset_for_tests() -> None:
    _TOOLS.clear()


def expose_action_as_tool(action_key: str) -> ToolSpec:
    """Look up a registered action and expose it as a tool."""
    return tool_for_action(get_action(action_key))
