//! Multimodal message types for the Peon agent loop.
//!
//! Supports text, images (URL and base64), audio, and video content
//! in both user and assistant messages. Designed to be serializable
//! for persistence and compatible with OpenAI-style chat APIs.

use serde::{Deserialize, Serialize};

// ==========================================
// Content Parts (Multimodal Building Blocks)
// ==========================================

/// A single piece of content within a message.
///
/// Messages can contain multiple `ContentPart`s to express multimodal input
/// (e.g., "What's in this image?" + an attached photo).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContentPart {
    /// Plain text content.
    #[serde(rename = "text")]
    Text { text: String },

    /// An image referenced by URL.
    #[serde(rename = "image_url")]
    ImageUrl {
        url: String,
        /// Optional detail level hint: "auto", "low", or "high".
        #[serde(skip_serializing_if = "Option::is_none")]
        detail: Option<String>,
    },

    /// An inline image encoded as base64.
    #[serde(rename = "image_base64")]
    ImageBase64 {
        /// Base64-encoded image data.
        data: String,
        /// MIME type, e.g., "image/png", "image/jpeg".
        media_type: String,
    },

    /// Audio content (base64-encoded).
    #[serde(rename = "audio")]
    Audio {
        /// Base64-encoded audio data.
        data: String,
        /// Audio format, e.g., "wav", "mp3", "ogg".
        format: String,
    },

    /// A video referenced by URL.
    #[serde(rename = "video_url")]
    VideoUrl { url: String },

    /// Inline video (base64-encoded).
    #[serde(rename = "video_base64")]
    VideoBase64 {
        /// Base64-encoded video data.
        data: String,
        /// MIME type, e.g., "video/mp4".
        media_type: String,
    },

    /// A file attachment (base64-encoded).
    #[serde(rename = "file")]
    File {
        /// Base64-encoded file data.
        data: String,
        /// MIME type, e.g., "application/pdf".
        media_type: String,
        /// Optional filename for display.
        #[serde(skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
    },
}

// ==========================================
// Assistant Content (LLM Response Parts)
// ==========================================

/// A piece of content returned by the LLM.
///
/// An assistant turn can contain a mix of text responses and tool call
/// requests. The agent loop partitions these to decide whether to
/// execute tools or return the final text.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum AssistantContent {
    /// A text response fragment.
    #[serde(rename = "text")]
    Text { text: String },

    /// A request to invoke a tool.
    #[serde(rename = "tool_call")]
    ToolCall {
        /// Unique ID for this tool call (used to match results).
        id: String,
        /// The tool name to invoke.      
        name: String,
        /// JSON-encoded arguments string generated by the LLM.
        arguments: String,
    },
}

// ==========================================
// Chat Messages
// ==========================================

/// A single message in the conversation history.
///
/// The agent loop maintains a `Vec<Message>` that grows as the conversation
/// progresses through user prompts, assistant responses, and tool results.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "role")]
pub enum Message {
    /// System-level instructions (preamble / system prompt).
    #[serde(rename = "system")]
    System { content: String },

    /// User input — supports multimodal content.
    #[serde(rename = "user")]
    User { content: Vec<ContentPart> },

    /// Assistant output — can contain text and/or tool calls.
    #[serde(rename = "assistant")]
    Assistant { content: Vec<AssistantContent> },

    /// The result of a tool execution, sent back to the LLM.
    #[serde(rename = "tool")]
    ToolResult {
        /// Must match the `id` from the corresponding `AssistantContent::ToolCall`.
        tool_call_id: String,
        /// The tool's output (usually a string, but could be JSON).
        content: String,
    },
}

// ==========================================
// Convenience Constructors
// ==========================================

impl Message {
    /// Create a system message.
    pub fn system(content: impl Into<String>) -> Self {
        Self::System {
            content: content.into(),
        }
    }

    /// Create a text-only user message.
    pub fn user_text(text: impl Into<String>) -> Self {
        Self::User {
            content: vec![ContentPart::Text { text: text.into() }],
        }
    }

    /// Create a user message with multiple content parts.
    pub fn user(parts: Vec<ContentPart>) -> Self {
        Self::User { content: parts }
    }

    /// Create a user message with text + an image URL.
    pub fn user_with_image(text: impl Into<String>, image_url: impl Into<String>) -> Self {
        Self::User {
            content: vec![
                ContentPart::Text { text: text.into() },
                ContentPart::ImageUrl {
                    url: image_url.into(),
                    detail: None,
                },
            ],
        }
    }

    /// Create a user message with text + inline base64 image.
    pub fn user_with_image_base64(
        text: impl Into<String>,
        data: impl Into<String>,
        media_type: impl Into<String>,
    ) -> Self {
        Self::User {
            content: vec![
                ContentPart::Text { text: text.into() },
                ContentPart::ImageBase64 {
                    data: data.into(),
                    media_type: media_type.into(),
                },
            ],
        }
    }

    /// Create a user message with text + audio data.
    pub fn user_with_audio(
        text: impl Into<String>,
        data: impl Into<String>,
        format: impl Into<String>,
    ) -> Self {
        Self::User {
            content: vec![
                ContentPart::Text { text: text.into() },
                ContentPart::Audio {
                    data: data.into(),
                    format: format.into(),
                },
            ],
        }
    }

    /// Create a user message with text + video URL.
    pub fn user_with_video(text: impl Into<String>, video_url: impl Into<String>) -> Self {
        Self::User {
            content: vec![
                ContentPart::Text { text: text.into() },
                ContentPart::VideoUrl {
                    url: video_url.into(),
                },
            ],
        }
    }

    /// Create a text-only assistant message.
    pub fn assistant_text(text: impl Into<String>) -> Self {
        Self::Assistant {
            content: vec![AssistantContent::Text { text: text.into() }],
        }
    }

    /// Create an assistant message with raw content parts.
    pub fn assistant(content: Vec<AssistantContent>) -> Self {
        Self::Assistant { content }
    }

    /// Create a tool result message.
    pub fn tool_result(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
        Self::ToolResult {
            tool_call_id: tool_call_id.into(),
            content: content.into(),
        }
    }

    /// Extract the plain text from a message, if it contains text.
    pub fn text(&self) -> Option<String> {
        match self {
            Self::System { content } => Some(content.clone()),
            Self::User { content } => {
                let texts: Vec<&str> = content
                    .iter()
                    .filter_map(|part| match part {
                        ContentPart::Text { text } => Some(text.as_str()),
                        _ => None,
                    })
                    .collect();
                if texts.is_empty() {
                    None
                } else {
                    Some(texts.join("\n"))
                }
            }
            Self::Assistant { content } => {
                let texts: Vec<&str> = content
                    .iter()
                    .filter_map(|part| match part {
                        AssistantContent::Text { text } => Some(text.as_str()),
                        _ => None,
                    })
                    .collect();
                if texts.is_empty() {
                    None
                } else {
                    Some(texts.join("\n"))
                }
            }
            Self::ToolResult { content, .. } => Some(content.clone()),
        }
    }
}

// ==========================================
// Conversions
// ==========================================

impl From<Vec<ContentPart>> for Message {
    /// Build a `Message::User` from a list of content parts.
    ///
    /// This allows multimodal inputs to be passed directly anywhere
    /// a `Message` or `impl Into<Message>` is expected, unifying
    /// the text-only and multimodal call paths.
    fn from(parts: Vec<ContentPart>) -> Self {
        Message::user(parts)
    }
}

impl ContentPart {
    /// Shorthand for text content.
    pub fn text(s: impl Into<String>) -> Self {
        Self::Text { text: s.into() }
    }

    /// Shorthand for image URL.
    pub fn image_url(url: impl Into<String>) -> Self {
        Self::ImageUrl {
            url: url.into(),
            detail: None,
        }
    }

    /// Shorthand for base64 image.
    pub fn image_base64(data: impl Into<String>, media_type: impl Into<String>) -> Self {
        Self::ImageBase64 {
            data: data.into(),
            media_type: media_type.into(),
        }
    }

    /// Shorthand for audio.
    pub fn audio(data: impl Into<String>, format: impl Into<String>) -> Self {
        Self::Audio {
            data: data.into(),
            format: format.into(),
        }
    }
}
