"""Message-related types.

Types for conversation messages and attachments.
"""

from __future__ import annotations

from enum import Enum
from typing import Literal

from pydantic import BaseModel

from core.types.enums import AttachmentType, MessageRole
from core.types.llm import LLMMessage, LLMToolCall, LLMToolCallFunction

# JSON-serializable value type for message content
MessageValue = str | int | float | bool | None | list[object] | dict[str, object]


# =============================================================================
# CONVERSATION MESSAGE (Pydantic for LLM compatibility)
# =============================================================================


class FunctionCall(BaseModel):
    """Function details within a tool call."""

    name: str
    arguments: str  # JSON string of arguments


class ToolCallMessage(BaseModel):
    """A tool call from an assistant message."""

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


class ConversationMessage(BaseModel):
    """A single message in conversation history.

    Used by loops for LLM context. Pydantic model for OpenAI compatibility.
    """

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

    def to_openai_format(self) -> dict[str, object]:
        """Convert to OpenAI-compatible message format.

        Note: This is kept because OpenAI API expects a specific format
        that differs slightly from model_dump().
        """
        role_str = self.role.value if isinstance(self.role, Enum) else str(self.role)
        msg: dict[str, object] = {"role": role_str}
        if self.content is not None:
            msg["content"] = self.content
        if self.name:
            msg["name"] = self.name
        if self.tool_call_id:
            msg["tool_call_id"] = self.tool_call_id
        if self.tool_calls:
            msg["tool_calls"] = [tc.model_dump(exclude_none=True) for tc in self.tool_calls]
        return msg

    def to_llm_message(self) -> LLMMessage:
        """Convert to the canonical LLMMessage model.

        Performs a proper model-to-model conversion without dict detour.
        """
        role_str = self.role.value if isinstance(self.role, Enum) else str(self.role)
        llm_tool_calls: list[LLMToolCall] | None = None
        if self.tool_calls:
            llm_tool_calls = [
                LLMToolCall(
                    id=tc.id,
                    type=tc.type,
                    function=LLMToolCallFunction(
                        name=tc.function.name,
                        arguments=tc.function.arguments,
                    ),
                )
                for tc in self.tool_calls
            ]
        return LLMMessage(
            role=role_str,  # type: ignore[arg-type]  # Literal narrowed by Pydantic validation
            content=self.content,
            name=self.name,
            tool_call_id=self.tool_call_id,
            tool_calls=llm_tool_calls,
        )


# =============================================================================
# ATTACHMENT
# =============================================================================


class Attachment(BaseModel):
    """An attachment (image, file, etc.) for LLM context."""

    type: AttachmentType | Literal["image", "file", "url"]
    content: str  # Base64 data, file path, or URL depending on type
    mime_type: str | None = None
    filename: str | None = None
    description: str | None = None


__all__ = [
    "Attachment",
    "ConversationMessage",
    "FunctionCall",
    "MessageValue",
    "ToolCallMessage",
]
