use crate::models::Provider;

use super::ModelId;

#[cfg(not(docsrs))]
#[allow(dead_code)]
mod capability_generated {
    include!(concat!(env!("OUT_DIR"), "/model_capabilities.rs"));
}

#[cfg(docsrs)]
#[allow(dead_code)]
mod capability_generated {
    #[derive(Clone, Copy)]
    pub struct Pricing {
        pub input: Option<f64>,
        pub output: Option<f64>,
        pub cache_read: Option<f64>,
        pub cache_write: Option<f64>,
    }

    #[derive(Clone, Copy)]
    pub struct Entry {
        pub provider: &'static str,
        pub id: &'static str,
        pub display_name: &'static str,
        pub description: &'static str,
        pub context_window: usize,
        pub max_output_tokens: Option<usize>,
        pub reasoning: bool,
        pub tool_call: bool,
        pub vision: bool,
        pub input_modalities: &'static [&'static str],
        pub caching: bool,
        pub structured_output: bool,
        pub pricing: Pricing,
    }

    pub const ENTRIES: &[Entry] = &[];
    pub const PROVIDERS: &[&str] = &[];

    pub fn metadata_for(_provider: &str, _id: &str) -> Option<Entry> {
        None
    }

    pub fn models_for_provider(_provider: &str) -> Option<&'static [&'static str]> {
        None
    }
}

/// Catalog metadata generated from `docs/models.json`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ModelPricing {
    pub input: Option<f64>,
    pub output: Option<f64>,
    pub cache_read: Option<f64>,
    pub cache_write: Option<f64>,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ModelCatalogEntry {
    pub provider: &'static str,
    pub id: &'static str,
    pub display_name: &'static str,
    pub description: &'static str,
    pub context_window: usize,
    pub max_output_tokens: Option<usize>,
    pub reasoning: bool,
    pub tool_call: bool,
    pub vision: bool,
    pub input_modalities: &'static [&'static str],
    pub caching: bool,
    pub structured_output: bool,
    pub pricing: ModelPricing,
}

fn catalog_provider_key(provider: &str) -> &str {
    if provider.eq_ignore_ascii_case("google") || provider.eq_ignore_ascii_case("gemini") {
        "gemini"
    } else if provider.eq_ignore_ascii_case("openai") {
        "openai"
    } else if provider.eq_ignore_ascii_case("anthropic") {
        "anthropic"
    } else if provider.eq_ignore_ascii_case("deepseek") {
        "deepseek"
    } else if provider.eq_ignore_ascii_case("openrouter") {
        "openrouter"
    } else if provider.eq_ignore_ascii_case("ollama") {
        "ollama"
    } else if provider.eq_ignore_ascii_case("lmstudio") {
        "lmstudio"
    } else if provider.eq_ignore_ascii_case("llamacpp")
        || provider.eq_ignore_ascii_case("llama.cpp")
    {
        "llamacpp"
    } else if provider.eq_ignore_ascii_case("moonshot") {
        "moonshot"
    } else if provider.eq_ignore_ascii_case("zai") {
        "zai"
    } else if provider.eq_ignore_ascii_case("minimax") {
        "minimax"
    } else if provider.eq_ignore_ascii_case("huggingface") {
        "huggingface"
    } else if provider.eq_ignore_ascii_case("stepfun") {
        "stepfun"
    } else if provider.eq_ignore_ascii_case("poolside") {
        "poolside"
    } else {
        provider
    }
}

fn capability_provider_key(provider: Provider) -> &'static str {
    match provider {
        Provider::Gemini => "gemini",
        Provider::OpenAI => "openai",
        Provider::Anthropic => "anthropic",
        Provider::Copilot => "copilot",
        Provider::DeepSeek => "deepseek",
        Provider::OpenRouter => "openrouter",
        Provider::Ollama => "ollama",
        Provider::LmStudio => "lmstudio",
        Provider::LlamaCpp => "llamacpp",
        Provider::Moonshot => "moonshot",
        Provider::ZAI => "zai",
        Provider::Minimax => "minimax",
        Provider::MiMo => "mimo",
        Provider::Mistral => "mistral",
        Provider::HuggingFace => "huggingface",
        Provider::OpenCodeZen => "opencode-zen",
        Provider::OpenCodeGo => "opencode-go",
        Provider::Qwen => "qwen",
        Provider::StepFun => "stepfun",
        Provider::Poolside => "poolside",
    }
}

fn generated_catalog_entry(provider: &str, id: &str) -> Option<ModelCatalogEntry> {
    capability_generated::metadata_for(catalog_provider_key(provider), id).map(|entry| {
        ModelCatalogEntry {
            provider: entry.provider,
            id: entry.id,
            display_name: entry.display_name,
            description: entry.description,
            context_window: entry.context_window,
            max_output_tokens: entry.max_output_tokens,
            reasoning: entry.reasoning,
            tool_call: entry.tool_call,
            vision: entry.vision,
            input_modalities: entry.input_modalities,
            caching: entry.caching,
            structured_output: entry.structured_output,
            pricing: ModelPricing {
                input: entry.pricing.input,
                output: entry.pricing.output,
                cache_read: entry.pricing.cache_read,
                cache_write: entry.pricing.cache_write,
            },
        }
    })
}

pub fn model_catalog_entry(provider: &str, id: &str) -> Option<ModelCatalogEntry> {
    generated_catalog_entry(provider, id)
}

pub fn supported_models_for_provider(provider: &str) -> Option<&'static [&'static str]> {
    capability_generated::models_for_provider(catalog_provider_key(provider))
}

pub fn catalog_provider_keys() -> &'static [&'static str] {
    capability_generated::PROVIDERS
}

impl ModelId {
    fn generated_capabilities(&self) -> Option<ModelCatalogEntry> {
        generated_catalog_entry(capability_provider_key(self.provider()), self.as_str())
    }

    /// Preferred built-in lightweight sibling or lower-tier fallback for this model.
    pub fn preferred_lightweight_variant(&self) -> Option<Self> {
        match self {
            ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => {
                Some(ModelId::Gemini31FlashLitePreview)
            }
            ModelId::GPT55 | ModelId::GPT54 | ModelId::GPT54Pro => Some(ModelId::GPT54Mini),
            ModelId::OpenCodeZenGPT54 => Some(ModelId::OpenCodeZenGPT54Mini),
            ModelId::GPT53Codex => Some(ModelId::GPT54Mini),
            ModelId::ClaudeOpus48 | ModelId::ClaudeSonnet46 | ModelId::ClaudeMythosPreview => {
                Some(ModelId::ClaudeHaiku45)
            }
            ModelId::CopilotGPT54 => Some(ModelId::CopilotGPT54Mini),
            ModelId::CopilotGPT52Codex | ModelId::CopilotGPT51CodexMax => {
                Some(ModelId::CopilotGPT54Mini)
            }
            ModelId::DeepSeekV4Pro => Some(ModelId::DeepSeekV4Flash),
            ModelId::HuggingFaceDeepseekV4ProTogether => {
                Some(ModelId::HuggingFaceDeepseekV4FlashNovita)
            }
            ModelId::OllamaDeepseekV4ProCloud => Some(ModelId::OllamaDeepseekV4FlashCloud),
            ModelId::ZaiGlm51 => Some(ModelId::ZaiGlm5),
            ModelId::MinimaxM27 => Some(ModelId::MinimaxM25),
            ModelId::OpenCodeGoMinimaxM27 => Some(ModelId::OpenCodeGoMinimaxM25),
            ModelId::StepFun37Flash => None,
            ModelId::PoolsideLagunaM1 => Some(ModelId::PoolsideLagunaXs2),
            _ => None,
        }
    }

    /// Attempt to find a non-reasoning variant for this model.
    pub fn non_reasoning_variant(&self) -> Option<Self> {
        if let Some(meta) = self.openrouter_metadata() {
            if !meta.reasoning {
                return None;
            }

            let vendor = meta.vendor;
            let mut candidates: Vec<Self> = Self::openrouter_vendor_groups()
                .into_iter()
                .find(|(candidate_vendor, _)| *candidate_vendor == vendor)
                .map(|(_, models)| {
                    models
                        .iter()
                        .copied()
                        .filter(|candidate| candidate != self)
                        .filter(|candidate| {
                            candidate
                                .openrouter_metadata()
                                .map(|other| !other.reasoning)
                                .unwrap_or(false)
                        })
                        .collect()
                })
                .unwrap_or_default();

            if candidates.is_empty() {
                return None;
            }

            candidates.sort_by_key(|candidate| {
                candidate
                    .openrouter_metadata()
                    .map(|data| (!data.efficient, data.display))
                    .unwrap_or((true, ""))
            });

            return candidates.into_iter().next();
        }

        let direct = match self {
            ModelId::Gemini31ProPreview
            | ModelId::Gemini31ProPreviewCustomTools
            | ModelId::Gemini31FlashLitePreview => Some(ModelId::Gemini35Flash),
            ModelId::GPT55
            | ModelId::GPT54
            | ModelId::GPT54Pro
            | ModelId::GPT54Nano
            | ModelId::GPT54Mini => Some(ModelId::GPT54Mini),
            ModelId::OpenCodeZenGPT54 => Some(ModelId::OpenCodeZenGPT54Mini),
            ModelId::CopilotGPT52Codex | ModelId::CopilotGPT54 => Some(ModelId::CopilotGPT54Mini),
            ModelId::DeepSeekV4Pro => Some(ModelId::DeepSeekV4Flash),
            ModelId::HuggingFaceDeepseekV4ProTogether => {
                Some(ModelId::HuggingFaceDeepseekV4FlashNovita)
            }
            ModelId::OllamaDeepseekV4ProCloud => Some(ModelId::OllamaDeepseekV4FlashCloud),
            ModelId::ZaiGlm5 | ModelId::ZaiGlm51 => Some(ModelId::OllamaGlm5Cloud),
            ModelId::ClaudeOpus48 | ModelId::ClaudeSonnet46 | ModelId::ClaudeMythosPreview => {
                Some(ModelId::ClaudeSonnet46)
            }
            ModelId::OpenCodeGoMinimaxM27 => Some(ModelId::OpenCodeGoMinimaxM25),
            ModelId::MinimaxM27 | ModelId::MinimaxM25 => None,
            _ => None,
        };

        direct.and_then(|candidate| {
            if candidate.supports_reasoning_effort() {
                None
            } else {
                Some(candidate)
            }
        })
    }

    /// Check if this is a "flash" variant (optimized for speed)
    pub fn is_flash_variant(&self) -> bool {
        matches!(
            self,
            ModelId::Gemini31FlashLitePreview
                | ModelId::Gemini35Flash
                | ModelId::OpenRouterStepfunStep35FlashFree
                | ModelId::OpenRouterNvidiaNemotron3Super120bA12bFree
                | ModelId::HuggingFaceStep35Flash
                | ModelId::StepFun37Flash
                | ModelId::HuggingFaceDeepseekV4FlashNovita
        )
    }

    /// Check if this is a "pro" variant (optimized for capability)
    pub fn is_pro_variant(&self) -> bool {
        matches!(
            self,
            ModelId::Gemini31ProPreview
                | ModelId::Gemini31ProPreviewCustomTools
                | ModelId::OpenRouterGoogleGemini31ProPreview
                | ModelId::GPT55
                | ModelId::GPT54
                | ModelId::GPT54Pro
                | ModelId::GPT53Codex
                | ModelId::CopilotGPT52Codex
                | ModelId::CopilotGPT51CodexMax
                | ModelId::CopilotGPT54
                | ModelId::CopilotClaudeSonnet46
                | ModelId::ClaudeOpus48
                | ModelId::ClaudeSonnet46
                | ModelId::ClaudeMythosPreview
                | ModelId::OpenCodeZenGPT54
                | ModelId::OpenCodeZenClaudeSonnet46
                | ModelId::OpenCodeZenGlm51
                | ModelId::OpenCodeGoGlm51
                | ModelId::OpenCodeGoMinimaxM27
                | ModelId::DeepSeekV4Pro
                | ModelId::ZaiGlm5
                | ModelId::ZaiGlm51
                | ModelId::OpenRouterStepfunStep35FlashFree
                | ModelId::OpenRouterNvidiaNemotron3Super120bA12bFree
                | ModelId::MinimaxM27
                | ModelId::MinimaxM25
                | ModelId::OpenCodeGoMinimaxM25
                | ModelId::OllamaGlm5Cloud
                | ModelId::OllamaGlm51Cloud
                | ModelId::OllamaNemotron3SuperCloud
                | ModelId::OllamaMinimaxM25Cloud
                | ModelId::HuggingFaceQwen3CoderNextNovita
                | ModelId::HuggingFaceQwen35397BA17BTogether
                | ModelId::HuggingFaceDeepseekV4ProTogether
                | ModelId::OpenRouterMoonshotaiKimiK26
                | ModelId::PoolsideLagunaM1
        )
    }

    /// Check if this is an optimized/efficient variant
    pub fn is_efficient_variant(&self) -> bool {
        if let Some(meta) = self.openrouter_metadata() {
            return meta.efficient;
        }
        matches!(
            self,
            ModelId::Gemini31FlashLitePreview
                | ModelId::Gemini35Flash
                | ModelId::GPT54Mini
                | ModelId::CopilotGPT54Mini
                | ModelId::ClaudeHaiku45
                | ModelId::OpenCodeZenGPT54Mini
                | ModelId::OpenCodeGoMinimaxM25
                | ModelId::DeepSeekV4Flash
                | ModelId::HuggingFaceStep35Flash
                | ModelId::HuggingFaceDeepseekV4FlashNovita
                | ModelId::PoolsideLagunaXs2
        )
    }

    /// Check if this is a top-tier model
    pub fn is_top_tier(&self) -> bool {
        if let Some(meta) = self.openrouter_metadata() {
            return meta.top_tier;
        }
        matches!(
            self,
            ModelId::Gemini31ProPreview
                | ModelId::Gemini31ProPreviewCustomTools
                | ModelId::OpenRouterGoogleGemini31ProPreview
                | ModelId::Gemini31FlashLitePreview
                | ModelId::Gemini35Flash
                | ModelId::GPT55
                | ModelId::GPT54
                | ModelId::GPT54Pro
                | ModelId::GPT53Codex
                | ModelId::ClaudeOpus48
                | ModelId::ClaudeSonnet46
                | ModelId::ClaudeMythosPreview
                | ModelId::OpenCodeZenGPT54
                | ModelId::OpenCodeZenClaudeSonnet46
                | ModelId::OpenCodeZenGlm51
                | ModelId::OpenCodeGoGlm51
                | ModelId::OpenCodeGoMinimaxM27
                | ModelId::DeepSeekV4Pro
                | ModelId::ZaiGlm5
                | ModelId::ZaiGlm51
                | ModelId::OpenRouterStepfunStep35FlashFree
                | ModelId::HuggingFaceQwen3CoderNextNovita
                | ModelId::HuggingFaceQwen35397BA17BTogether
                | ModelId::HuggingFaceDeepseekV4FlashNovita
                | ModelId::HuggingFaceDeepseekV4ProTogether
                | ModelId::OpenRouterMoonshotaiKimiK26
                | ModelId::PoolsideLagunaM1
        )
    }

    /// Determine whether the model is a reasoning-capable variant
    pub fn is_reasoning_variant(&self) -> bool {
        if let Some(meta) = self.openrouter_metadata() {
            return meta.reasoning;
        }
        self.provider().supports_reasoning_effort(self.as_str())
    }

    /// Determine whether the model supports tool calls/function execution
    pub fn supports_tool_calls(&self) -> bool {
        if let Some(meta) = self.generated_capabilities() {
            return meta.tool_call;
        }
        if let Some(meta) = self.openrouter_metadata() {
            return meta.tool_call;
        }
        true
    }

    /// Ordered list of supported input modalities when VT Code has metadata for this model.
    pub fn input_modalities(&self) -> &'static [&'static str] {
        self.generated_capabilities()
            .map(|meta| meta.input_modalities)
            .unwrap_or(&[])
    }

    /// Get the generation/version string for this model
    pub fn generation(&self) -> &'static str {
        if let Some(meta) = self.openrouter_metadata() {
            return meta.generation;
        }
        match self {
            // Gemini generations
            ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => "3.1",
            ModelId::Gemini31FlashLitePreview => "3.1-lite",
            // OpenAI generations
            ModelId::GPT55 => "5.5",
            ModelId::GPT54 | ModelId::GPT54Pro | ModelId::GPT54Nano | ModelId::GPT54Mini => "5.4",
            ModelId::GPT53Codex => "5.3",
            ModelId::OpenAIGptOss20b | ModelId::OpenAIGptOss120b => "5",
            // Anthropic generations
            ModelId::ClaudeOpus48 => "4.8",
            ModelId::ClaudeSonnet46 => "4.6",
            ModelId::ClaudeHaiku45 => "4.5",
            ModelId::ClaudeMythosPreview => "preview",
            // DeepSeek generations
            ModelId::DeepSeekV4Pro | ModelId::DeepSeekV4Flash => "4",
            // Z.AI generations
            ModelId::ZaiGlm5 => "5",
            ModelId::ZaiGlm51 => "5.1",
            ModelId::Gemini35Flash => "3.5",
            ModelId::OpenCodeZenGPT54 | ModelId::OpenCodeZenGPT54Mini => "5.4",
            ModelId::OpenCodeZenClaudeSonnet46 => "4.6",
            ModelId::OpenCodeZenGlm51 | ModelId::OpenCodeGoGlm51 => "5.1",
            ModelId::OpenCodeGoMinimaxM25 => "m2.5",
            ModelId::OpenCodeGoMinimaxM27 => "m2.7",
            ModelId::OllamaGptOss20b => "oss",
            ModelId::OllamaGptOss20bCloud => "oss-cloud",
            ModelId::OllamaGptOss120bCloud => "oss-cloud",
            ModelId::OllamaQwen317b => "oss",
            ModelId::OllamaQwen3CoderNext => "qwen3-coder-next:cloud",
            ModelId::OllamaDeepseekV4FlashCloud => "deepseek-v4-flash",
            ModelId::OllamaDeepseekV4ProCloud => "deepseek-v4-pro",
            ModelId::OllamaQwen3Next80bCloud => "qwen3-next",
            ModelId::OllamaMinimaxM2Cloud => "minimax-m2",
            ModelId::OllamaMinimaxM27Cloud => "minimax-m2.7",
            ModelId::OllamaGlm5Cloud => "glm-5",
            ModelId::OllamaGlm51Cloud => "glm-5.1",
            ModelId::OllamaMinimaxM25Cloud => "minimax-m2.5",
            ModelId::OllamaKimiK26Cloud => "kimi-k2.6",
            ModelId::OllamaNemotron3SuperCloud => "nemotron-3",
            ModelId::OllamaLagunaXs2 => "laguna-xs.2",
            ModelId::LlamaCppQwen3627b => "3.6",
            ModelId::LlamaCppQwen3635bA3b => "3.6",
            ModelId::LlamaCppGemma426bA4b => "4",
            ModelId::LlamaCppGemma4E4b => "4",
            ModelId::LlamaCppGptOss20b => "oss",
            ModelId::LlamaCppStep35Flash => "3.5",
            // MiniMax models
            ModelId::MinimaxM27 => "M2.7",
            ModelId::MinimaxM25 => "M2.5",
            // Moonshot models
            ModelId::MoonshotKimiK26 => "k2.6",
            // Hugging Face generations
            ModelId::HuggingFaceOpenAIGptOss20b => "oss",
            ModelId::HuggingFaceOpenAIGptOss120b => "oss",
            ModelId::HuggingFaceMinimaxM25Novita => "m2.5",
            ModelId::HuggingFaceGlm5Novita => "5",
            ModelId::HuggingFaceGlm51ZaiOrg => "5.1",
            ModelId::HuggingFaceKimiK26Novita => "k2.6",
            ModelId::HuggingFaceDeepseekV4FlashNovita => "v4-flash",
            ModelId::HuggingFaceDeepseekV4ProTogether => "v4-pro",
            ModelId::HuggingFaceStep35Flash => "3.5",
            ModelId::HuggingFaceQwen3CoderNextNovita | ModelId::OpenRouterQwen3CoderNext => {
                "qwen3-coder-next"
            }
            // Poolside models
            ModelId::PoolsideLagunaM1 => "laguna-m.1",
            ModelId::PoolsideLagunaXs2 => "laguna-xs.2",
            // Qwen models
            ModelId::Qwen37Max => "3.7",
            ModelId::Qwen36Flash | ModelId::Qwen36Plus => "3.6",
            ModelId::QwenDeepSeekV4Flash | ModelId::QwenDeepSeekV4Pro => "v4",
            ModelId::QwenGlm51 => "5.1",
            _ => "unknown",
        }
    }

    /// Determine if this model supports GPT-5.1+/5.2+/5.3+ shell tool type
    pub fn supports_shell_tool(&self) -> bool {
        matches!(
            self,
            ModelId::GPT55 | ModelId::GPT54 | ModelId::GPT54Pro | ModelId::GPT53Codex
        )
    }

    /// Determine if this model supports optimized apply_patch tool
    pub fn supports_apply_patch_tool(&self) -> bool {
        false // Placeholder for future optimization
    }
}
