use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum AiProvider {
    #[default]
    Ollama,
    Fireworks,
    OpenRouter,
}

/// Deserialize a provider string, coercing legacy `openai`/`anthropic` and any
/// unknown value to `Ollama`. Keeps existing config files loadable after the
/// stub providers were removed.
impl<'de> Deserialize<'de> for AiProvider {
    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let s = String::deserialize(d)?;
        Ok(match s.as_str() {
            "fireworks" => AiProvider::Fireworks,
            "openrouter" => AiProvider::OpenRouter,
            _ => AiProvider::Ollama,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiSettings {
    pub provider: AiProvider,
    pub ollama_url: String,
    #[serde(default)]
    pub fireworks_api_key: Option<String>,
    #[serde(default)]
    pub openrouter_api_key: Option<String>,
    pub model: String,
}

impl Default for AiSettings {
    fn default() -> Self {
        Self {
            provider: AiProvider::Ollama,
            ollama_url: "http://localhost:11434".to_string(),
            fireworks_api_key: None,
            openrouter_api_key: None,
            model: String::new(),
        }
    }
}

/// Generic chat message used by all chat providers (Ollama, Fireworks, OpenAI-compatible).
/// `{role, content}` shape is identical across these APIs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OllamaChatMessage {
    pub role: String,
    pub content: String,
}

#[derive(Debug, Clone, Serialize)]
pub struct OllamaChatRequest {
    pub model: String,
    pub messages: Vec<OllamaChatMessage>,
    pub stream: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct OllamaChatResponse {
    pub message: OllamaChatMessage,
}

#[derive(Debug, Deserialize)]
pub struct OllamaTagsResponse {
    pub models: Vec<OllamaModel>,
}

/// Generic chat-model descriptor exposed to the UI dropdown.
/// Reused as the return shape for both Ollama and Fireworks model listings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OllamaModel {
    pub name: String,
}

// ---------------------------------------------------------------------------
// OpenAI-compatible chat-completions (Fireworks, OpenRouter)
// These request/response shapes are shared by every OpenAI-compatible provider;
// the `Fireworks*` names are retained for historical reasons.
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize)]
pub struct FireworksChatRequest {
    pub model: String,
    pub messages: Vec<OllamaChatMessage>,
    pub temperature: f32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_format: Option<FireworksResponseFormat>,
}

#[derive(Debug, Clone, Serialize)]
pub struct FireworksResponseFormat {
    #[serde(rename = "type")]
    pub kind: String,
}

#[derive(Debug, Deserialize)]
pub struct FireworksChatResponse {
    pub choices: Vec<FireworksChoice>,
}

#[derive(Debug, Deserialize)]
pub struct FireworksChoice {
    pub message: OllamaChatMessage,
}

#[derive(Debug, Deserialize)]
pub struct FireworksModelsResponse {
    pub data: Vec<FireworksModelEntry>,
}

#[derive(Debug, Deserialize)]
pub struct FireworksModelEntry {
    pub id: String,
}
