use reqwest::Url;
use serde::{Deserialize, Serialize, de};
use time::OffsetDateTime;

use crate::{
    Content, Modality, Part,
    safety::{SafetyRating, SafetySetting},
};

/// Reason why generation finished
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FinishReason {
    /// Default value. This value is unused.
    FinishReasonUnspecified,
    /// Natural stop point of the model or provided stop sequence.
    Stop,
    /// The maximum number of tokens as specified in the request was reached.
    MaxTokens,
    /// The response candidate content was flagged for safety reasons.
    Safety,
    /// The response candidate content was flagged for recitation reasons.
    Recitation,
    /// The response candidate content was flagged for using an unsupported language.
    Language,
    /// Unknown reason.
    Other,
    /// Token generation stopped because the content contains forbidden terms.
    Blocklist,
    /// Token generation stopped for potentially containing prohibited content.
    ProhibitedContent,
    /// Token generation stopped because the content potentially contains Sensitive Personally Identifiable Information (SPII).
    Spii,
    /// The function call generated by the model is invalid.
    MalformedFunctionCall,
    /// Token generation stopped because the response was blocked by Model Armor.
    ModelArmor,
    /// Token generation stopped because generated images contain safety violations.
    ImageSafety,
    /// Model generated a tool call but no tools were enabled in the request.
    UnexpectedToolCall,
    /// Model called too many tools consecutively, thus the system exited execution.
    TooManyToolCalls,
}

impl FinishReason {
    fn from_wire_str(value: &str) -> Self {
        match value {
            "FINISH_REASON_UNSPECIFIED" => Self::FinishReasonUnspecified,
            "STOP" => Self::Stop,
            "MAX_TOKENS" => Self::MaxTokens,
            "SAFETY" => Self::Safety,
            "RECITATION" => Self::Recitation,
            "LANGUAGE" => Self::Language,
            "OTHER" => Self::Other,
            "BLOCKLIST" => Self::Blocklist,
            "PROHIBITED_CONTENT" => Self::ProhibitedContent,
            "SPII" => Self::Spii,
            "MALFORMED_FUNCTION_CALL" => Self::MalformedFunctionCall,
            "MODEL_ARMOR" => Self::ModelArmor,
            "IMAGE_SAFETY" => Self::ImageSafety,
            "UNEXPECTED_TOOL_CALL" => Self::UnexpectedToolCall,
            "TOO_MANY_TOOL_CALLS" => Self::TooManyToolCalls,
            _ => Self::Other,
        }
    }

    fn from_wire_number(value: i64) -> Self {
        match value {
            0 => Self::FinishReasonUnspecified,
            1 => Self::Stop,
            2 => Self::MaxTokens,
            3 => Self::Safety,
            4 => Self::Recitation,
            5 => Self::Other,
            6 => Self::Blocklist,
            7 => Self::ProhibitedContent,
            8 => Self::Spii,
            9 => Self::MalformedFunctionCall,
            10 => Self::ModelArmor,
            _ => Self::Other,
        }
    }
}

impl<'de> Deserialize<'de> for FinishReason {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = serde_json::Value::deserialize(deserializer)?;
        match value {
            serde_json::Value::String(s) => Ok(Self::from_wire_str(&s)),
            serde_json::Value::Number(n) => {
                n.as_i64().map(Self::from_wire_number).ok_or_else(|| {
                    de::Error::custom("finishReason must be an integer-compatible number")
                })
            }
            _ => Err(de::Error::custom("finishReason must be a string or integer")),
        }
    }
}

/// Citation metadata for content
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CitationMetadata {
    /// The citation sources
    #[serde(default)]
    pub citation_sources: Vec<CitationSource>,
}

/// Citation source
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CitationSource {
    /// The URI of the citation source
    pub uri: Option<String>,
    /// The title of the citation source
    pub title: Option<String>,
    /// The start index of the citation in the response
    pub start_index: Option<i32>,
    /// The end index of the citation in the response
    pub end_index: Option<i32>,
    /// The license of the citation source
    pub license: Option<String>,
    /// The publication date of the citation source
    #[serde(default, with = "time::serde::rfc3339::option")]
    pub publication_date: Option<OffsetDateTime>,
}

/// A candidate response
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Candidate {
    /// The content of the candidate
    #[serde(default)]
    pub content: Content,
    /// The safety ratings for the candidate
    #[serde(skip_serializing_if = "Option::is_none")]
    pub safety_ratings: Option<Vec<SafetyRating>>,
    /// The citation metadata for the candidate
    #[serde(skip_serializing_if = "Option::is_none")]
    pub citation_metadata: Option<CitationMetadata>,
    /// The grounding metadata for the candidate
    #[serde(skip_serializing_if = "Option::is_none")]
    pub grounding_metadata: Option<GroundingMetadata>,
    /// The finish reason for the candidate
    #[serde(skip_serializing_if = "Option::is_none")]
    pub finish_reason: Option<FinishReason>,
    /// The index of the candidate
    #[serde(skip_serializing_if = "Option::is_none")]
    pub index: Option<i32>,
}

/// Metadata about token usage
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UsageMetadata {
    /// The number of prompt tokens (null if request processing failed)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prompt_token_count: Option<i32>,
    /// The number of response tokens (null if generation failed)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub candidates_token_count: Option<i32>,
    /// The total number of tokens (null if individual counts unavailable)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_token_count: Option<i32>,
    /// The number of thinking tokens (Gemini 2.5 series only)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thoughts_token_count: Option<i32>,
    /// Detailed prompt token information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prompt_tokens_details: Option<Vec<PromptTokenDetails>>,
    /// The number of cached content tokens (batch API)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cached_content_token_count: Option<i32>,
    /// Detailed cache token information (batch API)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cache_tokens_details: Option<Vec<PromptTokenDetails>>,
}

/// Details about prompt tokens by modality
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PromptTokenDetails {
    /// The modality (e.g., "TEXT")
    pub modality: Modality,
    /// Token count for this modality
    pub token_count: i32,
}

/// Grounding metadata for responses that use grounding tools
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GroundingMetadata {
    /// Grounding chunks containing source information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub grounding_chunks: Option<Vec<GroundingChunk>>,
    /// Grounding supports connecting response text to sources
    #[serde(skip_serializing_if = "Option::is_none")]
    pub grounding_supports: Option<Vec<GroundingSupport>>,
    /// Web search queries used for grounding
    #[serde(skip_serializing_if = "Option::is_none")]
    pub web_search_queries: Option<Vec<String>>,
    /// Google Maps widget context token
    #[serde(skip_serializing_if = "Option::is_none")]
    pub google_maps_widget_context_token: Option<String>,
}

/// A chunk of grounding information from a source
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GroundingChunk {
    /// Maps-specific grounding information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub maps: Option<MapsGroundingChunk>,
    /// Web-specific grounding information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub web: Option<WebGroundingChunk>,
}

/// Maps-specific grounding chunk information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MapsGroundingChunk {
    /// The URI of the Maps source
    #[serde(default)]
    pub uri: Option<Url>,
    /// The title of the Maps source
    #[serde(default)]
    pub title: Option<String>,
    /// The place ID from Google Maps
    #[serde(skip_serializing_if = "Option::is_none")]
    pub place_id: Option<String>,
}

/// Web-specific grounding chunk information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct WebGroundingChunk {
    /// The URI of the web source
    #[serde(default)]
    pub uri: Option<Url>,
    /// The title of the web source
    #[serde(default)]
    pub title: Option<String>,
}

/// Support information connecting response text to grounding sources
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GroundingSupport {
    /// Segment of the response text
    pub segment: GroundingSegment,
    /// Indices of grounding chunks that support this segment
    pub grounding_chunk_indices: Vec<u32>,
}

/// A segment of response text
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct GroundingSegment {
    /// Start index of the segment in the response text
    #[serde(default)]
    pub start_index: Option<u32>,
    /// End index of the segment in the response text
    #[serde(default)]
    pub end_index: Option<u32>,
    /// The text content of the segment
    #[serde(default)]
    pub text: Option<String>,
}

/// Response from the Gemini API for content generation
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GenerationResponse {
    /// The candidates generated
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub candidates: Vec<Candidate>,
    /// The prompt feedback
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prompt_feedback: Option<PromptFeedback>,
    /// Usage metadata
    #[serde(skip_serializing_if = "Option::is_none")]
    pub usage_metadata: Option<UsageMetadata>,
    /// Model version used
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model_version: Option<String>,
    /// Response ID
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_id: Option<String>,
}

/// Reason why content was blocked
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BlockReason {
    /// Default value. This value is unused.
    BlockReasonUnspecified,
    /// Prompt was blocked due to safety reasons. Inspect safetyRatings to understand which safety category blocked it.
    Safety,
    /// Prompt was blocked due to unknown reasons.
    Other,
    /// Prompt was blocked due to the terms which are included from the terminology blocklist.
    Blocklist,
    /// Prompt was blocked due to prohibited content.
    ProhibitedContent,
    /// Prompt was blocked by Model Armor.
    ModelArmor,
    /// Prompt was blocked due to jailbreak detection.
    Jailbreak,
    /// Candidates blocked due to unsafe image generation content.
    ImageSafety,
}

impl BlockReason {
    fn from_wire_str(value: &str) -> Self {
        match value {
            "BLOCK_REASON_UNSPECIFIED" | "BLOCKED_REASON_UNSPECIFIED" => {
                Self::BlockReasonUnspecified
            }
            "SAFETY" => Self::Safety,
            "OTHER" => Self::Other,
            "BLOCKLIST" => Self::Blocklist,
            "PROHIBITED_CONTENT" => Self::ProhibitedContent,
            "MODEL_ARMOR" => Self::ModelArmor,
            "JAILBREAK" => Self::Jailbreak,
            "IMAGE_SAFETY" => Self::ImageSafety,
            _ => Self::Other,
        }
    }

    fn from_wire_number(value: i64) -> Self {
        match value {
            0 => Self::BlockReasonUnspecified,
            1 => Self::Safety,
            2 => Self::Other,
            3 => Self::Blocklist,
            4 => Self::ProhibitedContent,
            5 => Self::ModelArmor,
            6 => Self::Jailbreak,
            7 => Self::ImageSafety,
            _ => Self::Other,
        }
    }
}

impl<'de> Deserialize<'de> for BlockReason {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = serde_json::Value::deserialize(deserializer)?;
        match value {
            serde_json::Value::String(s) => Ok(Self::from_wire_str(&s)),
            serde_json::Value::Number(n) => {
                n.as_i64().map(Self::from_wire_number).ok_or_else(|| {
                    de::Error::custom("blockReason must be an integer-compatible number")
                })
            }
            _ => Err(de::Error::custom("blockReason must be a string or integer")),
        }
    }
}

/// Feedback about the prompt
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PromptFeedback {
    /// The safety ratings for the prompt
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub safety_ratings: Vec<SafetyRating>,
    /// The block reason if the prompt was blocked
    #[serde(skip_serializing_if = "Option::is_none")]
    pub block_reason: Option<BlockReason>,
}

impl GenerationResponse {
    /// Get the text of the first candidate
    pub fn text(&self) -> String {
        self.candidates
            .first()
            .and_then(|c| {
                c.content.parts.as_ref().and_then(|parts| {
                    parts.first().and_then(|p| match p {
                        Part::Text { text, thought: _, thought_signature: _ } => Some(text.clone()),
                        _ => None,
                    })
                })
            })
            .unwrap_or_default()
    }

    /// Get function calls from the response
    pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
        self.candidates
            .iter()
            .flat_map(|c| {
                c.content
                    .parts
                    .as_ref()
                    .map(|parts| {
                        parts
                            .iter()
                            .filter_map(|p| match p {
                                Part::FunctionCall { function_call, thought_signature: _ } => {
                                    Some(function_call)
                                }
                                _ => None,
                            })
                            .collect::<Vec<_>>()
                    })
                    .unwrap_or_default()
            })
            .collect()
    }

    /// Get function calls with their thought signatures from the response
    pub fn function_calls_with_thoughts(
        &self,
    ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
        self.candidates
            .iter()
            .flat_map(|c| {
                c.content
                    .parts
                    .as_ref()
                    .map(|parts| {
                        parts
                            .iter()
                            .filter_map(|p| match p {
                                Part::FunctionCall { function_call, thought_signature } => {
                                    Some((function_call, thought_signature.as_ref()))
                                }
                                _ => None,
                            })
                            .collect::<Vec<_>>()
                    })
                    .unwrap_or_default()
            })
            .collect()
    }

    /// Get thought summaries from the response
    pub fn thoughts(&self) -> Vec<String> {
        self.candidates
            .iter()
            .flat_map(|c| {
                c.content
                    .parts
                    .as_ref()
                    .map(|parts| {
                        parts
                            .iter()
                            .filter_map(|p| match p {
                                Part::Text { text, thought: Some(true), thought_signature: _ } => {
                                    Some(text.clone())
                                }
                                _ => None,
                            })
                            .collect::<Vec<_>>()
                    })
                    .unwrap_or_default()
            })
            .collect()
    }

    /// Get all text parts (both regular text and thoughts)
    pub fn all_text(&self) -> Vec<(String, bool)> {
        self.candidates
            .iter()
            .flat_map(|c| {
                c.content
                    .parts
                    .as_ref()
                    .map(|parts| {
                        parts
                            .iter()
                            .filter_map(|p| match p {
                                Part::Text { text, thought, thought_signature: _ } => {
                                    Some((text.clone(), thought.unwrap_or(false)))
                                }
                                _ => None,
                            })
                            .collect::<Vec<_>>()
                    })
                    .unwrap_or_default()
            })
            .collect()
    }

    /// Get text parts with their thought signatures from the response
    pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
        self.candidates
            .iter()
            .flat_map(|c| {
                c.content
                    .parts
                    .as_ref()
                    .map(|parts| {
                        parts
                            .iter()
                            .filter_map(|p| match p {
                                Part::Text { text, thought, thought_signature } => Some((
                                    text.clone(),
                                    thought.unwrap_or(false),
                                    thought_signature.as_ref(),
                                )),
                                _ => None,
                            })
                            .collect::<Vec<_>>()
                    })
                    .unwrap_or_default()
            })
            .collect()
    }
}

/// Request to generate content
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GenerateContentRequest {
    /// The contents to generate content from
    pub contents: Vec<Content>,
    /// The generation config
    #[serde(skip_serializing_if = "Option::is_none")]
    pub generation_config: Option<GenerationConfig>,
    /// The safety settings
    #[serde(skip_serializing_if = "Option::is_none")]
    pub safety_settings: Option<Vec<SafetySetting>>,
    /// The tools that the model can use
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tools: Option<Vec<crate::tools::Tool>>,
    /// The tool config
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_config: Option<crate::tools::ToolConfig>,
    /// The system instruction
    #[serde(skip_serializing_if = "Option::is_none")]
    pub system_instruction: Option<Content>,
    /// The cached content to use
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cached_content: Option<String>,
}

impl GenerateContentRequest {
    /// Strips fields that Vertex AI does not support.
    ///
    /// The Vertex AI surface (`aiplatform.googleapis.com`) rejects
    /// `includeServerSideToolInvocations` with a 400 error. Vertex AI handles
    /// built-in tools (Google Search, URL Context, etc.) natively without
    /// needing this flag. This method clears the flag so the request can be
    /// sent to Vertex AI without modification.
    ///
    /// AI Studio (`generativelanguage.googleapis.com`) requires the flag for
    /// Gemini 3 models to return `toolCall`/`toolResponse` parts instead of
    /// silently truncating the response.
    pub fn strip_vertex_unsupported_fields(&mut self) {
        if let Some(tc) = &mut self.tool_config {
            tc.include_server_side_tool_invocations = None;
        }
    }
}

/// Native thinking level for Gemini 3 models.
///
/// Controls the amount of reasoning effort the model applies. This is the
/// Gemini 3 native thinking control — for Gemini 2.5 budget-based thinking,
/// use [`ThinkingConfig::with_thinking_budget`] instead.
///
/// Serializes as lowercase per the Gemini API contract
/// (e.g., `"low"`, `"high"`).
///
/// Available levels (model support varies):
/// - `Minimal` — matches "no thinking" for most queries; model may still
///   think minimally for complex coding tasks. Not supported on Gemini 3.1 Pro.
/// - `Low` — minimizes latency and cost; best for simple tasks.
/// - `Medium` — balanced thinking for most tasks.
/// - `High` — maximizes reasoning depth (default for Gemini 3 Flash and 3.1 Pro).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ThinkingLevel {
    /// Minimal reasoning effort. Matches "no thinking" for most queries.
    /// Not supported on Gemini 3.1 Pro.
    Minimal,
    /// Low reasoning effort. Best for simple instruction following and chat.
    Low,
    /// Medium reasoning effort. Balanced thinking for most tasks.
    Medium,
    /// High reasoning effort — maximizes reasoning depth (default).
    High,
}

/// Configuration for thinking (Gemini 2.5 and 3 series)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThinkingConfig {
    /// The thinking budget (number of thinking tokens)
    ///
    /// This is the Gemini 2.5 budget-based thinking control.
    ///
    /// - Set to 0 to disable thinking
    /// - Set to -1 for dynamic thinking (model decides)
    /// - Set to a positive number for a specific token budget
    ///
    /// Model-specific ranges:
    /// - 2.5 Pro: 128 to 32768 (cannot disable thinking)
    /// - 2.5 Flash: 0 to 24576
    /// - 2.5 Flash Lite: 512 to 24576
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thinking_budget: Option<i32>,

    /// Whether to include thought summaries in the response
    ///
    /// When enabled, the response will include synthesized versions of the model's
    /// raw thoughts, providing insights into the reasoning process.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub include_thoughts: Option<bool>,

    /// Native thinking level for Gemini 3 models.
    ///
    /// When set, the model uses level-based reasoning instead of a token budget.
    /// Do not combine with `thinking_budget` — use one or the other.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thinking_level: Option<ThinkingLevel>,
}

impl ThinkingConfig {
    /// Validate the thinking configuration.
    ///
    /// Returns an error if both `thinking_budget` and `thinking_level` are set,
    /// since they are mutually exclusive controls (budget for Gemini 2.5, level for Gemini 3).
    pub fn validate(&self) -> Result<(), String> {
        if self.thinking_budget.is_some() && self.thinking_level.is_some() {
            return Err(
                "thinking_budget and thinking_level are mutually exclusive; use one or the other"
                    .to_string(),
            );
        }
        Ok(())
    }

    /// Create a new thinking config with default settings
    pub fn new() -> Self {
        Self { thinking_budget: None, include_thoughts: None, thinking_level: None }
    }

    /// Set the thinking budget (Gemini 2.5 budget-based control)
    pub fn with_thinking_budget(mut self, budget: i32) -> Self {
        self.thinking_budget = Some(budget);
        self
    }

    /// Enable dynamic thinking (model decides the budget)
    pub fn with_dynamic_thinking(mut self) -> Self {
        self.thinking_budget = Some(-1);
        self
    }

    /// Include thought summaries in the response
    pub fn with_thoughts_included(mut self, include: bool) -> Self {
        self.include_thoughts = Some(include);
        self
    }

    /// Set the thinking level (Gemini 3 native level-based control).
    ///
    /// This is the preferred control for Gemini 3 models. Do not combine
    /// with `with_thinking_budget` — use one or the other.
    pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
        self.thinking_level = Some(level);
        self
    }

    /// Create a thinking config that enables dynamic thinking with thoughts included
    pub fn dynamic_thinking() -> Self {
        Self { thinking_budget: Some(-1), include_thoughts: Some(true), thinking_level: None }
    }
}

impl Default for ThinkingConfig {
    fn default() -> Self {
        Self::new()
    }
}

/// Configuration for generation
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GenerationConfig {
    /// The temperature for the model (0.0 to 1.0)
    ///
    /// Controls the randomness of the output. Higher values (e.g., 0.9) make output
    /// more random, lower values (e.g., 0.1) make output more deterministic.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub temperature: Option<f32>,

    /// The top-p value for the model (0.0 to 1.0)
    ///
    /// For each token generation step, the model considers the top_p percentage of
    /// probability mass for potential token choices. Lower values are more selective,
    /// higher values allow more variety.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top_p: Option<f32>,

    /// The top-k value for the model
    ///
    /// For each token generation step, the model considers the top_k most likely tokens.
    /// Lower values are more selective, higher values allow more variety.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top_k: Option<i32>,

    /// The maximum number of tokens to generate
    ///
    /// Limits the length of the generated content. One token is roughly 4 characters.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_output_tokens: Option<i32>,

    /// The candidate count
    ///
    /// Number of alternative responses to generate.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub candidate_count: Option<i32>,

    /// Whether to stop on specific sequences
    ///
    /// The model will stop generating content when it encounters any of these sequences.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop_sequences: Option<Vec<String>>,

    /// The response mime type
    ///
    /// Specifies the format of the model's response.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_mime_type: Option<String>,
    /// The response schema
    ///
    /// Specifies the JSON schema for structured responses.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_schema: Option<serde_json::Value>,

    /// Response modalities (for TTS and other multimodal outputs)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_modalities: Option<Vec<String>>,

    /// Speech configuration for text-to-speech generation
    #[serde(skip_serializing_if = "Option::is_none")]
    pub speech_config: Option<SpeechConfig>,

    /// The thinking configuration
    ///
    /// Configuration for the model's thinking process (Gemini 2.5 series only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thinking_config: Option<ThinkingConfig>,
}

impl GenerationConfig {
    /// Validate the generation configuration.
    ///
    /// Returns an error if any parameter is outside its valid range:
    /// - `temperature`: must be between 0.0 and 2.0
    /// - `top_p`: must be between 0.0 and 1.0
    /// - `top_k`: must be positive
    /// - `max_output_tokens`: must be positive
    ///
    /// If `thinking_config` is present, delegates to [`ThinkingConfig::validate`] as well.
    /// All `None` fields are accepted without error.
    pub fn validate(&self) -> Result<(), String> {
        if let Some(t) = self.temperature
            && !(0.0..=2.0).contains(&t)
        {
            return Err("temperature must be between 0.0 and 2.0".to_string());
        }
        if let Some(p) = self.top_p
            && !(0.0..=1.0).contains(&p)
        {
            return Err("top_p must be between 0.0 and 1.0".to_string());
        }
        if let Some(k) = self.top_k
            && k <= 0
        {
            return Err("top_k must be positive".to_string());
        }
        if let Some(m) = self.max_output_tokens
            && m <= 0
        {
            return Err("max_output_tokens must be positive".to_string());
        }
        if let Some(ref tc) = self.thinking_config {
            tc.validate()?;
        }
        Ok(())
    }
}

/// Configuration for speech generation (text-to-speech)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SpeechConfig {
    /// Single voice configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub voice_config: Option<VoiceConfig>,
    /// Multi-speaker voice configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
}

/// Voice configuration for text-to-speech
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VoiceConfig {
    /// Prebuilt voice configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
}

/// Prebuilt voice configuration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PrebuiltVoiceConfig {
    /// The name of the voice to use
    pub voice_name: String,
}

/// Multi-speaker voice configuration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MultiSpeakerVoiceConfig {
    /// Configuration for each speaker
    pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
}

/// Configuration for a specific speaker in multi-speaker TTS
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SpeakerVoiceConfig {
    /// The name of the speaker (must match the name used in the prompt)
    pub speaker: String,
    /// Voice configuration for this speaker
    pub voice_config: VoiceConfig,
}

impl SpeechConfig {
    /// Create a new speech config with a single voice
    pub fn single_voice(voice_name: impl Into<String>) -> Self {
        Self {
            voice_config: Some(VoiceConfig {
                prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
            }),
            multi_speaker_voice_config: None,
        }
    }

    /// Create a new speech config with multiple speakers
    pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
        Self {
            voice_config: None,
            multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
                speaker_voice_configs: speakers,
            }),
        }
    }
}

impl SpeakerVoiceConfig {
    /// Create a new speaker voice configuration
    pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
        Self {
            speaker: speaker.into(),
            voice_config: VoiceConfig {
                prebuilt_voice_config: Some(PrebuiltVoiceConfig { voice_name: voice_name.into() }),
            },
        }
    }
}
