"""Utilities for resolving tools and schemas by name.

Provides resolve_tool and resolve_json_schema helpers, plus
ResolverFailedError for when a resolver callable raises.
"""

from collections.abc import Callable
from typing import Any

from dotpromptz.typing import (
    JsonSchema,
    SchemaResolver,
    ToolDefinition,
    ToolResolver,
)

# Resolver callable type.
ResolverCallable = Callable[[str], Any]


class ResolverFailedError(RuntimeError):
    """Raised when a resolver function fails to resolve an object.

    This exception wraps errors that occur during the resolution of tools
    or schemas. It preserves the original error context while

    Attributes:
        name: The name of the object that failed to resolve (e.g., tool name).
        kind: The category of object ('tool' or 'schema').
        reason: A human-readable description of why resolution failed.
    """

    def __init__(self, name: str, kind: str, reason: str) -> None:
        """Initialize the error with resolution context.

        Args:
            name: The name of the object that failed to resolve.
            kind: The kind of object that failed to resolve
                  ('tool' or 'schema').
            reason: The reason the object resolver failed.
        """
        self.name = name
        self.kind = kind
        self.reason = reason
        super().__init__(f'{kind} resolver failed for {name}; {reason}')

    def __str__(self) -> str:
        """Return a human-readable error message.

        Returns:
            A formatted string describing the resolution failure.
        """
        return f'{self.kind} resolver failed for {self.name}; {self.reason}'

    def __repr__(self) -> str:
        """Return a detailed string representation for debugging.

        Returns:
            A formatted string with full error details.
        """
        return f'ResolverFailedError(name={self.name!r}, kind={self.kind!r}, reason={self.reason!r})'


def resolve[ResolverT: ResolverCallable, DefinitionT](
    name: str,
    kind: str,
    resolver: ResolverT | None,
) -> DefinitionT:
    """Resolves a single object using the provided resolver.

    Args:
        name: The name of the object to resolve.
        kind: The kind of object to resolve.
        resolver: The object resolver callable.

    Returns:
        The resolved object.

    Raises:
        LookupError: If the resolver returns None for the object.
        ResolverFailedError: For exceptions raised by the resolver.
        TypeError: If the resolver is not callable or returns an invalid type.
        ValueError: If the resolver is not defined.
    """
    obj: DefinitionT | None = None

    if resolver is None:
        raise ValueError(f'{kind} resolver is not defined')

    if not callable(resolver):
        raise TypeError(f"{kind} resolver for '{name}' is not callable")

    try:
        obj = resolver(name)
    except Exception as e:
        raise ResolverFailedError(name, kind, str(e)) from e

    # Raise when the resolver indicates the object doesn't exist.
    if obj is None:
        raise LookupError(f"{kind} resolver for '{name}' returned None")

    return obj


def resolve_tool(name: str, resolver: ToolResolver | None) -> ToolDefinition:
    """Resolve a tool using the provided resolver.

    Args:
        name: The name of the tool to resolve.
        resolver: The tool resolver callable.

    Returns:
        The resolved tool definition.

    Raises:
        LookupError: If the resolver returns None for the tool.
        ResolverFailedError: For exceptions raised by the resolver.
        TypeError: If the resolver is not callable or returns an invalid type.
        ValueError: If the resolver is not defined.
    """
    return resolve(name, 'tool', resolver)


def resolve_json_schema(name: str, resolver: SchemaResolver | None) -> JsonSchema:
    """Resolve a JSON schema using the provided resolver.

    Args:
        name: The name of the JSON schema to resolve.
        resolver: The JSON schema resolver callable.

    Returns:
        The resolved JSON schema.

    Raises:
        LookupError: If the resolver returns None for the schema.
        ResolverFailedError: For exceptions raised by the resolver.
        TypeError: If the resolver is not callable or returns an invalid type.
    """
    return resolve(name, 'schema', resolver)
