"""Message-related types.

Types for conversation messages and attachments.
"""

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",
]
