"""Tool DTOs - data shapes for tool calling.

Defines the shape of tool requests and results.
Nodes import these; they don't define their own.

NO DICTS - ALL PYDANTIC.
"""

from typing import Literal

from pydantic import BaseModel, Field

# =============================================================================
# TYPED ARGUMENT VALUES (no dict[str, Any])
# =============================================================================

# JSON-serializable argument value - typed, not Any
ToolArgValue = str | int | float | bool | None | list[object] | dict[str, object]


# =============================================================================
# TOOL ARGS MODEL (Pydantic-first handler arguments)
# =============================================================================


class ToolArgs(BaseModel):
    """Pydantic model for tool handler arguments.

    Accepts dynamic fields from LLM function calls via extra="allow".
    Tool handlers receive this instead of raw dicts.

    Usage:
        async def handler(args: ToolArgs, ctx: Context) -> ToolOutput:
            query = args.get("query", "")
            ...
    """

    model_config = {"extra": "allow"}

    def get(self, key: str, default: ToolArgValue = None) -> ToolArgValue:
        """Dict-like get for dynamic field access."""
        extra = self.__pydantic_extra__ or {}
        if key in extra:
            return extra[key]
        if hasattr(self, key):
            return getattr(self, key)
        return default


# =============================================================================
# TOOL OUTPUT METADATA
# =============================================================================


class ToolOutputMetadata(BaseModel):
    """Typed metadata for tool output."""

    execution_time_ms: float | None = None
    source: str | None = None
    cached: bool = False


class ToolOutput(BaseModel):
    """Canonical output contract for ALL tools.

    Important: this lives in `core.pathways.models` so YAML + VM code can share a
    single surface. To avoid circular imports, `core/__init__.py` must stay minimal
    (no heavy re-exports that pull in loops/pathways at import time).
    """

    model_config = {"extra": "forbid"}

    success: bool = Field(default=True)
    data: ToolArgValue = Field(default=None, description="Tool-specific payload")
    error: str | None = Field(default=None, description="Error message if failed")
    metadata: ToolOutputMetadata = Field(default_factory=ToolOutputMetadata)


# =============================================================================
# TOOL CALLS AND RESULTS
# =============================================================================


class ToolCall(BaseModel):
    """A single tool call request."""

    model_config = {"extra": "forbid"}

    id: str = Field(description="Unique call ID for correlation")
    tool: str = Field(description="Tool name (e.g., 'pathway.create')")
    args: dict[str, ToolArgValue] = Field(default_factory=dict, description="Tool arguments")


class ToolResult(BaseModel):
    """Result of a tool call."""

    model_config = {"extra": "forbid"}

    call_id: str = Field(description="ID of the corresponding ToolCall")
    tool: str = Field(description="Tool that was called")
    success: bool = Field(default=True)
    output: ToolArgValue = Field(default=None, description="Tool output")
    error: str | None = Field(default=None, description="Error message if failed")


class ToolCallPlan(BaseModel):
    """Plan containing multiple tool calls."""

    model_config = {"extra": "forbid"}

    calls: list[ToolCall] = Field(default_factory=list)
    reasoning: str | None = Field(default=None, description="Why these tools are needed")


class ToolExecutionResult(BaseModel):
    """Results of executing a tool plan."""

    model_config = {"extra": "forbid"}

    results: list[ToolResult] = Field(default_factory=list)
    all_succeeded: bool = Field(default=True)
    summary: str | None = Field(default=None)


# =============================================================================
# TOOL SCHEMA (OpenAI format)
# =============================================================================


class ParameterProperty(BaseModel):
    """A single parameter property in a tool schema."""

    type: str
    description: str | None = None
    enum: list[str] | None = None
    default: str | int | float | bool | None = None


class ToolParameters(BaseModel):
    """Parameters definition for a tool schema (JSON Schema subset)."""

    type: Literal["object"] = "object"
    properties: dict[str, ParameterProperty] = Field(default_factory=dict)
    required: list[str] = Field(default_factory=list)
    additionalProperties: bool = False


class ToolFunction(BaseModel):
    """Function definition within a tool schema."""

    name: str
    description: str | None = None
    parameters: ToolParameters = Field(default_factory=ToolParameters)


class ToolSchema(BaseModel):
    """OpenAI-format tool schema."""

    type: Literal["function"] = "function"
    function: ToolFunction

    def to_openai_format(self) -> dict[str, object]:
        """Convert to OpenAI-compatible tool format."""
        return self.model_dump(exclude_none=True)


# =============================================================================
# TOOL CALL RECORD (audit trail)
# =============================================================================


class ToolCallRecord(BaseModel):
    """Record of a tool invocation for audit/replay."""

    tool_name: str
    arguments: dict[str, ToolArgValue]
    success: bool
    output: ToolArgValue = None
    error: str | None = None
    duration_ms: float | None = None
    call_id: str | None = None


# =============================================================================
# REGISTERED TOOL (runtime-registered dynamic tools)
# =============================================================================


class ToolParamInfo(BaseModel):
    """Metadata about a single tool parameter."""

    name: str
    type: str = "any"


class RegisteredTool(BaseModel):
    """A dynamically registered tool tracked in RunState."""

    path: str
    name: str
    params: list[ToolParamInfo] = Field(default_factory=list)
    description: str | None = None
    is_async: bool = False


__all__ = [
    "ParameterProperty",
    "RegisteredTool",
    "ToolArgValue",
    "ToolArgs",
    "ToolCall",
    "ToolCallPlan",
    "ToolCallRecord",
    "ToolExecutionResult",
    "ToolFunction",
    "ToolOutput",
    "ToolOutputMetadata",
    "ToolParamInfo",
    "ToolParameters",
    "ToolResult",
    "ToolSchema",
]
