"""In-memory tool registry for CleverAgents.

Thread-safe registry that stores
[ToolSpec][cleveragents.tool.runtime.ToolSpec] instances keyed by their
namespaced name. Supports registration, lookup, listing with optional
filters, removal, and polymorphic resource-type matching.

## Operations

| Method                      | Description                                       |
|-----------------------------|---------------------------------------------------|
| ``register(spec)``          | Add a tool; raises ``ToolError`` on name collision|
| ``get(name)``               | Lookup by namespaced name; returns ``None`` if missing |
| ``list_tools(...)``         | List with optional namespace/type filters         |
| ``find_tools_for_resource`` | Polymorphic tool matching by resource type (ADR-042) |
| ``remove(name)``            | Unregister; returns ``bool``                      |

All mutating operations are protected by ``threading.RLock``.

See Also:
    - ADR-042: Resource Type Inheritance (§Tool Binding Polymorphism)
"""

from __future__ import annotations

import threading
from typing import Any

from cleveragents.resource.inheritance import is_subtype_of
from cleveragents.tool.runtime import ToolError, ToolSpec


class ToolRegistry:
    """In-memory registry for built-in tools.

    All mutating operations are protected by a reentrant lock so the
    registry is safe to use from multiple threads.
    """

    def __init__(self) -> None:
        self._tools: dict[str, ToolSpec] = {}
        self._lock = threading.RLock()

    def register(self, tool_spec: ToolSpec) -> None:
        """Add a tool to the registry.

        Raises ``ToolError`` if a tool with the same name is already
        registered (name collision detection).
        """
        with self._lock:
            if tool_spec.name in self._tools:
                raise ToolError(
                    tool_name=tool_spec.name,
                    error_type="RegistrationError",
                    details=f"Tool '{tool_spec.name}' is already registered",
                )
            self._tools[tool_spec.name] = tool_spec

    def get(self, name: str) -> ToolSpec | None:
        """Lookup a tool by its namespaced name."""
        with self._lock:
            return self._tools.get(name)

    def list_tools(
        self,
        namespace: str | None = None,
        tool_type: str | None = None,
        source: str | None = None,
    ) -> list[ToolSpec]:
        """List registered tools with optional filters.

        Parameters
        ----------
        namespace:
            If provided, only return tools whose name starts with
            ``namespace/``.
        tool_type:
            If provided, only return tools whose ``tool_type`` matches
            (``"tool"`` or ``"validation"``). When ``None``, all tools
            are returned regardless of type.
        source:
            If provided, only return tools whose ``source`` field matches.
        """
        with self._lock:
            specs = list(self._tools.values())

        if namespace is not None:
            prefix = f"{namespace}/"
            specs = [s for s in specs if s.name.startswith(prefix)]

        if source is not None:
            specs = [s for s in specs if s.source == source]

        if tool_type is not None:
            specs = [s for s in specs if s.tool_type == tool_type]

        return specs

    def find_tools_for_resource(
        self,
        resource_type: str,
        type_registry: dict[str, Any],
    ) -> list[ToolSpec]:
        """Find tools whose resource bindings accept *resource_type*.

        Implements ADR-042 §Tool Binding Polymorphism: a tool bound to
        a parent type (e.g., ``container-instance``) also matches any
        subtype (e.g., ``devcontainer-instance``).

        Resource bindings are stored in ``tool.source_metadata`` under
        the key ``"resource_bindings"`` as a list of dicts with a
        ``"resource_type"`` field.

        Args:
            resource_type: The concrete resource type name to match.
            type_registry: Dict mapping type names to their definitions
                (must contain ``inherits`` keys for chain walking).

        Returns:
            List of matching ``ToolSpec`` instances.
        """
        with self._lock:
            specs = list(self._tools.values())

        matched: list[ToolSpec] = []
        for spec in specs:
            bindings = spec.source_metadata.get("resource_bindings", [])
            if not bindings:
                continue
            for binding in bindings:
                slot_type = binding.get("resource_type", "")
                if is_subtype_of(resource_type, slot_type, type_registry):
                    matched.append(spec)
                    break  # One matching binding is enough
        return matched

    def remove(self, name: str) -> bool:
        """Remove a tool from the registry.

        Returns ``True`` if the tool was found and removed, ``False``
        otherwise.
        """
        with self._lock:
            if name in self._tools:
                del self._tools[name]
                return True
            return False
