from __future__ import annotations

"""Registry of named tools available to an agent."""

from dataclasses import dataclass
from typing import Any

from agentrspy.core import AgentError, Tool, ToolDefinition, ToolOutput


class IntoToolOutput:
    """Converts common Python values into `ToolOutput`."""

    @staticmethod
    def convert(value: Any) -> ToolOutput:
        if isinstance(value, ToolOutput):
            return value
        if isinstance(value, str):
            return ToolOutput.text(value)
        return ToolOutput.text(str(value))


@dataclass(slots=True)
class ToolRegistry:
    """In-memory registry of tools."""

    tools: dict[str, Tool]

    def __init__(self) -> None:
        self.tools = {}

    @classmethod
    def new(cls) -> "ToolRegistry":
        """Create an empty registry."""

        return cls()

    def register(self, tool: Tool) -> "ToolRegistry":
        """Register a tool and return the registry."""

        self.tools[tool.name()] = tool
        return self

    def register_boxed(self, tool: Tool) -> "ToolRegistry":
        """Register an already created tool instance."""

        return self.register(tool)

    def get(self, name: str) -> Tool | None:
        """Look up a tool by name."""

        return self.tools.get(name)

    def contains(self, name: str) -> bool:
        """Return whether a tool exists."""

        return name in self.tools

    def len(self) -> int:
        """Return the number of registered tools."""

        return len(self.tools)

    def is_empty(self) -> bool:
        """Return whether no tools are registered."""

        return not self.tools

    def to_definitions(self) -> list[ToolDefinition]:
        """Convert the registry to LLM-facing tool definitions."""

        return [
            ToolDefinition(
                name=tool.name(), description=tool.description(), schema=tool.schema()
            )
            for tool in self.tools.values()
        ]

    async def call(self, name: str, input_value: Any) -> ToolOutput:
        """Call a tool by name."""

        tool = self.get(name)
        if tool is None:
            raise AgentError.tool_not_found(name)
        return await tool.call(input_value)

    def merge(self, other: "ToolRegistry") -> "ToolRegistry":
        """Merge two registries."""

        self.tools.update(other.tools)
        return self

    async def register_mcp(self, command: str) -> "ToolRegistry":
        """Register all tools exposed by an MCP server."""

        from agentrspy.mcp import connect_mcp_tools, spawn_mcp_tools

        tools = await (
            connect_mcp_tools(command)
            if command.startswith(("http://", "https://"))
            else spawn_mcp_tools(command)
        )
        for tool in tools:
            self.register_boxed(tool)
        return self

    async def register_mcp_http(self, endpoint: str) -> "ToolRegistry":
        """Register all tools exposed by a web MCP endpoint."""

        from agentrspy.mcp import connect_mcp_tools

        for tool in await connect_mcp_tools(endpoint):
            self.register_boxed(tool)
        return self

    async def register_mcp_http_with_api_key(
        self, endpoint: str, api_key: str
    ) -> "ToolRegistry":
        """Register MCP tools from a web endpoint using an API key."""

        from agentrspy.mcp import connect_mcp_tools_with_api_key

        for tool in await connect_mcp_tools_with_api_key(endpoint, api_key):
            self.register_boxed(tool)
        return self

    async def register_mcp_http_with_options(
        self, endpoint: str, options: Any
    ) -> "ToolRegistry":
        """Register MCP tools from a web endpoint with full transport options."""

        from agentrspy.mcp import connect_mcp_tools_with_options

        for tool in await connect_mcp_tools_with_options(endpoint, options):
            self.register_boxed(tool)
        return self
