"""The catalogue of capabilities an agent may choose from.

Explicit registration, not auto-discovery by directory scan: a Tool that becomes
available merely by existing on disk is a Tool nobody decided to ship.
"""

from __future__ import annotations

from app.tools.base import ExecutionPolicy, Tool


class ToolRegistry:
    """Holds the Tools available to this deployment."""

    def __init__(self) -> None:
        self._tools: dict[str, Tool] = {}

    def register(self, tool: Tool) -> None:
        if tool.name in self._tools:
            # Silent replacement would let a half-finished Tool shadow a working
            # one with no sign anything had changed.
            raise ValueError(f"A tool named '{tool.name}' is already registered.")

        self._tools[tool.name] = tool

    def get(self, name: str) -> Tool:
        if name not in self._tools:
            raise KeyError(f"No tool named '{name}'. Available: {', '.join(sorted(self._tools))}")

        return self._tools[name]

    def has(self, name: str) -> bool:
        return name in self._tools

    def all(self) -> list[Tool]:
        return sorted(self._tools.values(), key=lambda t: t.name)

    def executable(self) -> list[Tool]:
        """Tools an agent may run unattended right now.

        What an agent should be *offered*. Approval-gated and disabled Tools are
        registered and describable, but listing them as available invites a model
        to plan around capabilities it cannot exercise.
        """
        return [t for t in self.all() if t.policy is ExecutionPolicy.AUTOMATIC]

    def describe(self) -> list[dict[str, str]]:
        """The catalogue, for prompting or for an operator.

        Level 1 data only (ADR-0002): names, descriptions and policies. Nothing
        here identifies a person, so it is safe to send to a hosted model.
        """
        return [
            {"name": t.name, "description": t.description, "policy": t.policy.value}
            for t in self.all()
        ]
