use serde::{Deserialize, Serialize};
use std::fmt;

// ---------------------------------------------------------------------------
// Content types: The ATOMIC UNITS of all messages. Every message is composed of Vec<Content>.
// ---------------------------------------------------------------------------

// ═══════════════════════════════════════════════════════
// CONTENT ENUM — the atomic unit of all message payloads
// Every message is composed of Vec<Content> variants.
//
// ── Text ────────────────────────────────────────────────
// Plain string payload sent to/from LLM.
// Example:
//   let c = Content::Text("Summarize this code".to_string());
//
// ── Image ───────────────────────────────────────────────
// Binary image encoded as base64 string (NOT a file path).
// LLMs like GPT-4o receive image bytes inline via base64.
// Example:
//   let c = Content::Image {
//       data: base64::encode(fs::read("pic.png").unwrap()),
//       mime_type: "image/png".to_string(),
//   };
//
// ── Thinking ────────────────────────────────────────────
// Internal chain-of-thought from the LLM (e.g. Claude's
// extended thinking). Visible in UI, never re-sent to LLM.
// Example:
//   let c = Content::Thinking("I should first check the schema...".to_string());
//
// ── ToolCall ────────────────────────────────────────────
// LLM's request to invoke a tool with structured JSON args.
// Example:
//   let c = Content::ToolCall {
//       id: "call_abc123".to_string(),
//       name: "read_file".to_string(),
//       params: json!({ "path": "src/main.rs" }),
//   };
// ═══════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════
// DERIVE MACROS — auto-generated trait implementations
// #[derive(...)] runs at compile time, zero runtime cost.
//
// Debug      → enables {:?} printing for logging/debug
// Clone      → enables .clone() for deep copying the type
// PartialEq  → enables == / != comparisons between instances
// Serialize  → enables conversion TO json/bytes (serde crate)
// Deserialize→ enables construction FROM json/bytes (serde crate)
//
// Python analogy: like @dataclass(eq=True) + json mixin,
// but declared explicitly and enforced by the compiler.
// ═══════════════════════════════════════════════════════
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")] // serde : for serialization, use the "type" field to determine the variant
pub enum Content {
    #[serde(rename = "text")]
    Text { text: String },
    #[serde(rename = "image")]
    Image {
        data: String,
        #[serde(rename = "mimeType")]
        mime_type: String,
    },
    #[serde(rename = "thinking")]
    Thinking {
        /// The raw thinking/reasoning text generated by the model
        /// before its final response. Visible to the user/UI but
        /// treated as internal monologue — NOT part of the answer.
        thinking: String,

        /// Cryptographic integrity token issued by the LLM provider.
        /// Anthropic: base64 blob called `signature` on the thinking block.
        /// OpenAI:    opaque blob called `encrypted_content` in reasoning items.
        /// Gemini:    opaque blob called `thought_signature`.
        ///
        /// Must be echoed back UNMODIFIED in multi-turn conversations —
        /// the provider uses it to verify the thinking block was not
        /// altered between turns. `None` when provider doesn't support
        /// extended thinking or on first-turn generation.
        #[serde(skip_serializing_if = "Option::is_none")]
        signature: Option<String>, //Option<T> works for any type T in Rust: enum, struct, primitive, or even another Option
    },
    #[serde(rename = "toolCall")]
    ToolCall {
        id: String,
        name: String,
        arguments: serde_json::Value,
    },
}

/* CONCEPTUAL NOTE: ENUMs vs Structs in Rust
- Enums: OR Types
define a type that can be one of several variants, each with its own data.
Perfect for Content, where a block can be Text OR Image OR Thinking, etc.

- Structs: AND Types
define a type that has multiple fields simultaneously.
Perfect for Message, where a message has a role AND content AND timestamp, etc.
 */

// ---------------------------------------------------------------------------
// Messages: The units of conversation between user and assistant.
// Each message has a role (user, assistant, toolResult) and a payload (Vec<Content>).
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "role")]
pub enum Message {
    // The payload of a message is always a Vec<Content> — a sequence of atomic content blocks.
    // Vec<Content> is just Rust's name for a dynamic array (like Python's list) of Content enums.
    //The reason a Message holds a list of Content (not just one) is that a single LLM turn can contain multiple things at once
    // — e.g., a Thinking block followed by a ToolCall followed by Text .
    // Anthropic's extended thinking API is a real example of this:
    // one assistant message = [Thinking("..."), Text("here's my answer")]
    /// User turn — raw input from the human.
    /// Vec<Content> allows mixed media: text + images in one message.
    /// timestamp: unix millis, used for session branching / replay.
    #[serde(rename = "user")]
    User {
        content: Vec<Content>,
        timestamp: u64,
    },

    /// Assistant turn — the LLM's response, fully annotated.
    /// stop_reason tells you WHY generation stopped (tool_call, end_turn, max_tokens).
    /// model/provider/usage are captured here for cost tracking & multi-provider routing.
    /// error_message is optional — a failed turn is still persisted, not dropped.
    #[serde(rename = "assistant")]
    Assistant {
        content: Vec<Content>,
        #[serde(rename = "stopReason")]
        stop_reason: StopReason, //enum defined below: Stop, Length, ToolUse, Error, Aborted
        model: String,
        provider: String,
        usage: super::usage::Usage, // struct defined below: input/output tokens, cache hits, total tokens
        timestamp: u64,
        #[serde(skip_serializing_if = "Option::is_none")]
        error_message: Option<String>,
    },

    /// Tool execution result — returned to LLM in the next turn.
    /// tool_call_id links this result back to the specific ToolCall in Assistant content.
    /// is_error: true means LLM sees the failure and can recover/retry.
    /// content: Vec<Content> allows rich results — text, images, structured data.
    #[serde(rename = "toolResult")]
    ToolResult {
        #[serde(rename = "toolCallId")]
        tool_call_id: String,
        #[serde(rename = "toolName")]
        tool_name: String,
        content: Vec<Content>,
        #[serde(rename = "isError")]
        is_error: bool,
        timestamp: u64,
    },
}

// Helper constructors for common message types
impl Message {
    // impl Message { ... }	"attach methods to this type". Python analogy: class Message: body — defining methods

    // Helper constructor for a simple user message with just text content. Constructor returns Self.
    // Here it's Self::User { ... } because we're constructing a Message::User variant.
    // The content is a Vec<Content> with one Content::Text block containing the input text.
    // Timestamp is set to now_ms() helper function defined below.
    //****************************************************************************************************
    // The user() constructor is a convenient way to create a Message::User with just text content.
    // NOTE: there is NO constructor for Assistant or ToolResult messages —
    // these require more fields (stop_reason, model, etc.) that must be provided explicitly when constructing those variants.
    // The design lesson: only create constructors where there's a default/common shape worth abstracting. Don't add constructors for complex types just for symmetry.
    pub fn user(text: impl Into<String>) -> Self {
        //impl Into<String> means "accept anything that can be converted into a String"
        // — this makes the API more flexible, allowing both &str and String inputs.
        Self::User {
            // For a simple user message, we wrap the text in a Vec<Content> with one Text block.
            // This allows the API to evolve later to support mixed content (e.g., text + image) without breaking changes.

            // vec! — a macro (the ! means macro not a function. macros generate code at compile time) that creates
            // and populates a Vec in one shot, like Python's [item] literal syntax.
            // Here we create a Vec<Content> with a single Content::Text block containing the user's input text.
            // vec![Content::Text { text: text.into() }] is exactly Python's [Content.Text(text=str(text))] — create a list with one item inside it,
            // where that item is a Content::Text struct with its text field set to the input text.
            content: vec![Content::Text { text: text.into() }],
            timestamp: now_ms(),
        }
    }

    /// Parse the assistant's response as structured JSON of type `T`.
    ///
    /// Used as the read-side counterpart to `StreamConfig.response_format`:
    ///
    /// - For providers with native JSON modes (OpenAI, Google), the text content
    ///   carries the JSON object — this method `serde_json::from_str`s it directly.
    /// - For the Anthropic tool-call emulation, the JSON is in the `arguments` of
    ///   a synthetic `respond_json` tool call — this method prefers that path
    ///   when present.
    ///
    /// Returns `ProviderError::SchemaMismatch` with a descriptive `reason` on any
    /// failure mode (not an assistant message, no text/tool-call content, invalid
    /// JSON, deserialisation mismatch).
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #[derive(serde::Deserialize)]
    /// struct Answer { score: i32, label: String }
    ///
    /// let msg: phi_core::Message = /* obtained from a JsonObject-formatted call */;
    /// let parsed: Answer = msg.extract_json()?;
    /// ```
    pub fn extract_json<T: serde::de::DeserializeOwned>(
        &self,
    ) -> Result<T, crate::provider::ProviderError> {
        use crate::provider::ProviderError;
        let assistant_content = match self {
            Self::Assistant { content, .. } => content,
            _ => {
                return Err(ProviderError::SchemaMismatch {
                    reason: "extract_json requires an Assistant message".into(),
                });
            }
        };

        // Prefer the tool-call emulation path: a `respond_json` synthetic tool call
        // (used by the Anthropic JSON-mode adapter). Its `arguments` is already a
        // serde_json::Value so we can serde::Deserialize straight into T without a
        // string round-trip.
        if let Some(args) = assistant_content.iter().find_map(|c| match c {
            Content::ToolCall {
                name, arguments, ..
            } if name == "respond_json" => Some(arguments.clone()),
            _ => None,
        }) {
            return serde_json::from_value(args).map_err(|e| ProviderError::SchemaMismatch {
                reason: format!(
                    "respond_json tool-call arguments did not deserialize: {}",
                    e
                ),
            });
        }

        // Fallback: concatenate every Text block and parse as JSON. Most providers
        // return a single text block under JsonObject / JsonSchema mode.
        let mut buf = String::new();
        for c in assistant_content {
            if let Content::Text { text } = c {
                buf.push_str(text);
            }
        }
        if buf.trim().is_empty() {
            return Err(ProviderError::SchemaMismatch {
                reason: "message has no text or respond_json tool-call to parse as JSON".into(),
            });
        }
        serde_json::from_str(&buf).map_err(|e| ProviderError::SchemaMismatch {
            reason: format!("assistant text is not valid JSON: {}", e),
        })
    }

    pub fn role(&self) -> &str {
        //& means borrow — "look at this value without taking ownership of it".
        //*************************************************************
        // Rust's ownership model means passing self (no &) would consume and destroy the value after the call.
        // &self : allows us to read the message's role without taking ownership, so we can call this method multiple times or on borrowed references.
        // &mut self : would allow us to modify the message's internal state if needed (not the case here, but useful for methods that change the message).
        // self: would mean "take ownership of the message" — the caller would no longer have access to it after calling role(),
        // which is not what we want for a simple getter method. The self gets deleted after the call, so you can only call it once and can't use the message again.
        // *************************************************************
        // &str on the return side means "return a reference to a string slice that already exists in memory". Similar to borrowing — no allocation, no copy.
        match self {
            // The .. means "ignore the other fields (only signature), we only care about the variant type here"
            Self::User { .. } => "user",
            //The serde vs role() Sync Problem
            // Yes, the strings must match exactly — the LLM API expects "user" in both the serialized JSON and the role field returned by the role() method.
            // This is because the LLM provider uses the "role" field to determine how to process the message (e.g., treat "user" messages as input from the human, "assistant
            Self::Assistant { .. } => "assistant",
            Self::ToolResult { .. } => "toolResult",
        }
    }

    /***************** ALTERNATIVES to role() implementation *****************
    // An althernate version with serde_json::Value for dynamic access to fields, but it's less efficient and more error-prone than the direct match approach above:
    use serde_json::to_value;
    pub fn role(&self) -> String {
        to_value(self)
            .unwrap()["role"]           // reads the serde-serialized "role" key
            .as_str()
            .unwrap()
            .to_string()
    }


    // An althernate more production ready (fault tolerant) implementation of role() that returns a Result and validates the internal consistency of the message:
    use serde_json
    pub fn role(&self) -> Result<String, serde_json::Error> {
        let value = serde_json::to_value(self)?;
        let role = value["role"]
            .as_str()  // as_str() → borrows the string, no memory allocation, just a pointer+length view
            .ok_or_else(|| serde_json::Error::custom("role field missing or not a string"))?;
            // COMMENT - ||: The || pipes are the closure's (similar to  = python'S lambda) empty argument list — |arg1, arg2|
        Ok(role.to_string()) // to_string() creates a new owned String from the borrowed &str, which is what the method signature says we return.
    }

    The caller expects a Result<String, serde_json::Error> and not a string and therefore needs to handle the Result with a "match expression" or the "?" operator.

    Think of "?" as a way to propagate errors up the call stack without having to write explicit error handling code at each step.
    When you call a function that returns a Result, you can append ? to it. If the Result is Ok, it unwraps the value and continues execution.
    If the Result is Err, it immediately returns that error from the current function, effectively propagating it up to the caller.

    Python analogy:
    Think of "? as Python's:
    if result is None:
        raise SomeError("role missing")  # ← that's what ? does
    else:
        role = result
        # continue with role
    */

    //*********************************************************************

    // Helper method to check if an assistant message ended due to context overflow.
    // Using "exhaustive pattern matching" (match; Some(msg); flow to specifc here: crate::provider::is_context_overflow_message(msg))
    // to safely access the CONTEXT OVERFLOW error_message.

    /// Check if this assistant message represents a context overflow error.
    ///
    /// Some providers (SSE-based: Anthropic, OpenAI) return overflow as a
    /// `StopReason::Error` message rather than an HTTP error. This method
    /// checks the `error_message` field against known overflow patterns.
    pub fn is_context_overflow(&self) -> bool {
        match self {
            Self::Assistant {
                stop_reason: StopReason::Error,
                error_message: Some(msg),
                //Some(msg) inside a match arm says: "only enter this branch if error_message is not None,
                // and if so, bind the inner string to the variable msg for use in the arm's body"
                ..
            } => crate::provider::is_context_overflow_message(msg),
            _ => false, // Rust match must be exhaustive — we have to handle all possible variants of Message.
        }
    }
}

// ---------------------------------------------------------------------------
// Stop reasons
// ---------------------------------------------------------------------------

/* StopReason enum captures why an assistant message generation stopped. This is crucial for understanding the LLM's behavior and for debugging.
LLMS vary in how they signal the end of generation — some use explicit stop reasons, others return errors for context overflow, etc.
By standardizing these into a StopReason enum, we can handle them uniformly in the agent loop and UI.
However, providers differ in how they indicate context overflow — some return a StopReason::Error with a specific error message,
while others might have a dedicated stop reason.
Therefore, we also have provider specific implmentation and he is_context_overflow() method on Message to check for overflow conditions in
a provider-agnostic way, by inspecting the error_message field when stop_reason is Error.
*/

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum StopReason {
    Stop,
    Length, //MaxTokens, TokenLimit, ContextOverflow — different providers use different terms for the same underlying reason: the generation
    ToolUse,
    Error,
    Aborted,
    /* Below are the options adopted from other Agentic models and our own experience with LLM APIs.*/
    MaxTurns, // Adopted from: LangGraph, Autogen. Description: Indicates generation stopped because the conversation reached the maximum allowed turns.
    UserStop, // Adopted from: AutoGPT. Description: Indicates generation was stopped by an explicit user command (e.g., "stop", "cancel").
    Handoff, // OpenAI Swarm, Adopted from: AutoGPT. Description: Indicates the agent is handing off to a human operator (e.g., for approval or intervention).
    GuardRail, // Adopted from: Llama Guard, Bedrock. Description: Indicates generation was stopped by an internal guardrail (e.g., content moderation, safety filter) - — different from Aborted
    ContextCompacted, // Adopted from: Anthropic's context compaction feature. Description: Indicates generation stopped because the model compacted the context to fit within limits, which may have caused loss of information.
    Paused, // Adopted from: LangGraph. Description: Indicates generation was paused (e.g., waiting for external input or async operation) rather than fully stopped.
}

//*********** STOP REASON HELPERS ***********
/*
fmt::Display — Rust's "how to print this type as a human-readable string"

Python analogy: implementing __str__(self) on a class.

In Rust, you use fmt::Display instead of __str__. Once implemented, you can:
  - println!("{}", stop_reason)          ← uses Display
  - format!("Stopped: {}", stop_reason) ← uses Display
  - stop_reason.to_string()              ← auto-generated via Display!

The signature:
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

  &self           — borrow the value (read-only)
  f               — the "output buffer" you write into
  fmt::Result     — Ok(()) if write succeeded, Err if not (rare)
  fmt::Formatter<'_> — '_ is an elided (compiler-inferred) lifetime for the borrow

The write!() macro writes into f (the formatter buffer):
  write!(f, "stop")  ←→  Python's  return "stop"

We implement this manually (instead of deriving) because we want custom
camelCase strings ("toolUse", not "ToolUse") that match the wire protocol.
*/
impl fmt::Display for StopReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Stop => write!(f, "stop"),
            Self::Length => write!(f, "length"),
            Self::ToolUse => write!(f, "toolUse"),
            Self::Error => write!(f, "error"),
            Self::Aborted => write!(f, "aborted"),
            Self::MaxTurns => write!(f, "maxTurns"),
            Self::UserStop => write!(f, "userStop"),
            Self::Handoff => write!(f, "handoff"),
            Self::GuardRail => write!(f, "guardRail"),
            Self::ContextCompacted => write!(f, "contextCompacted"),
            Self::Paused => write!(f, "paused"),
        }
    }
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/*
now_ms() — current time as Unix milliseconds (ms since Jan 1, 1970 UTC).

RUST QUIRK: Rust's time API is intentionally explicit about fallibility.
`duration_since(UNIX_EPOCH)` returns a Result<Duration, SystemTimeError>
because in theory the system clock could be set before the epoch (negative).
`.unwrap_or_default()` handles this gracefully: if it fails, use Duration::ZERO
(timestamp = 0) rather than panicking. Panicking in a timestamp helper would
be a terrible user experience.

Chain breakdown:
  SystemTime::now()              → current system time (platform-specific)
  .duration_since(UNIX_EPOCH)   → Result<Duration, SystemTimeError>
  .unwrap_or_default()           → Duration (0 on error)
  .as_millis()                   → u128 (milliseconds as 128-bit integer)
  as u64                         → truncate to 64-bit (safe until year 584,542,046)

Python analogy:
  import time
  now_ms = int(time.time() * 1000)
*/
pub fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}
