"""LLM-specific types - Layer 0 (no internal dependencies).

Canonical Pydantic models for LLM interactions:
- Messages (chat format)
- Tool calls / tool specs (OpenAI function calling format)
- Request/Response envelopes

These live in core.types so core.llm.providers can import them
without circular dependencies.
"""

from typing import Literal

from pydantic import BaseModel, Field

from core.common import JsonObject

# =============================================================================
# TOOL CALL MODELS (OpenAI function calling format)
# =============================================================================


class LLMToolCallFunction(BaseModel):
    """Function details within an LLM tool call."""

    name: str
    arguments: str  # JSON string of arguments


class LLMToolCall(BaseModel):
    """A tool call from an LLM response (OpenAI format)."""

    id: str
    type: Literal["function"] = "function"
    function: LLMToolCallFunction


# =============================================================================
# TOOL SPEC MODELS (sent to LLM for function calling)
# =============================================================================


class LLMParameterProperty(BaseModel):
    """A single parameter property in a tool spec."""

    model_config = {"extra": "allow"}

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


class LLMFunctionParameters(BaseModel):
    """Parameters definition for an LLM function spec (JSON Schema subset)."""

    model_config = {"extra": "allow"}

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


class LLMFunctionSpec(BaseModel):
    """Function definition within a tool spec."""

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


class LLMToolSpec(BaseModel):
    """OpenAI-format tool spec sent to the LLM."""

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


# =============================================================================
# TOOL CHOICE
# =============================================================================


class ToolChoiceFunction(BaseModel):
    """Specific function choice."""

    name: str


class ToolChoiceObject(BaseModel):
    """Tool choice as object (pick a specific function)."""

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


# Tool choice: string ("auto", "none", "required") or specific function
ToolChoice = str | ToolChoiceObject


# =============================================================================
# MESSAGE MODELS
# =============================================================================


class LLMMessage(BaseModel):
    """A single message in an LLM conversation (OpenAI chat format)."""

    role: Literal["system", "user", "assistant", "tool"]
    content: str | None = None
    name: str | None = None  # For tool messages
    tool_call_id: str | None = None  # For tool response messages
    tool_calls: list[LLMToolCall] | None = None  # For assistant messages with tool calls


# =============================================================================
# REQUEST / RESPONSE ENVELOPES
# =============================================================================


class LLMRequest(BaseModel):
    """Typed request to an LLM provider."""

    system: str
    messages: list[LLMMessage]
    model: str = "reasoning"
    temperature: float = 0.7
    tools: list[LLMToolSpec] | None = None
    tool_choice: ToolChoice | None = None
    max_tokens: int = 4096
    images: list[str] | None = None
    response_format: Literal["text", "json"] = "text"


class LLMResponse(BaseModel):
    """Typed response from an LLM provider."""

    content: str = ""
    tool_calls: list[LLMToolCall] = Field(default_factory=list)
    tokens_used: int = 0
    input_tokens: int = 0
    output_tokens: int = 0
    model: str | None = None
    finish_reason: str | None = None


# =============================================================================
# GENERATE RESULT (canonical return type from providers)
# =============================================================================


class LLMGenerateResult(BaseModel):
    """Normalized result from LLM generation.

    This is the canonical return type from all LLM providers.
    Pydantic model (not dataclass) — validates at construction,
    serializes with model_dump().
    """

    content: str
    model: str
    tokens_used: int
    cost_usd: float
    input_tokens: int = 0
    output_tokens: int = 0
    provider: str = "openrouter"
    finish_reason: str | None = None
    tool_calls: list[LLMToolCall] | None = None
    metadata: JsonObject = Field(default_factory=dict)


class LLMStreamChunk(BaseModel):
    """A single chunk from a streaming LLM response.

    Yielded by ``LLMProvider.generate_stream()`` as tokens arrive.
    Providers that report usage in the final chunk should populate
    ``input_tokens`` / ``output_tokens`` so the pipeline can track cost.
    """

    delta: str = ""
    finish_reason: str | None = None
    tool_calls: list[LLMToolCall] | None = None
    input_tokens: int = 0
    output_tokens: int = 0


class LLMEmbedResult(BaseModel):
    """Normalized result from embedding generation."""

    vectors: list[list[float]]
    model: str
    tokens_used: int
    cost_usd: float
    provider: str = "openrouter"


__all__ = [
    "LLMEmbedResult",
    "LLMFunctionParameters",
    "LLMFunctionSpec",
    "LLMGenerateResult",
    "LLMMessage",
    "LLMParameterProperty",
    "LLMRequest",
    "LLMResponse",
    "LLMStreamChunk",
    "LLMToolCall",
    "LLMToolCallFunction",
    "LLMToolSpec",
    "ToolChoice",
    "ToolChoiceFunction",
    "ToolChoiceObject",
]
