pub mod claude;
pub mod codex;
pub mod cursor;
pub mod deepseek;
pub mod emerging;
pub mod gemini;
mod generic_json;
pub mod kimi;
pub mod kiro;
pub mod opencode;

use crate::provider::Provider;

const PROVIDER_IDS: &[&str] = &[
    "claude",
    "codex",
    "cline",
    "cursor",
    "opencode",
    "kiro",
    "deepseek",
    "kimi",
    "gemini",
    "antigravity",
    "copilot",
    "windsurf",
    "cidebuddy",
    "codebuddy",
    "qoder",
    "qwen",
    "trae",
    "trae_gui",
    "traecn",
    "droid",
    "codybuddycn",
    "stepfun",
    "workbuddy",
    "hermes",
    "pi",
    "omp",
];

pub struct ProviderRegistry;

impl ProviderRegistry {
    pub fn ids() -> &'static [&'static str] {
        PROVIDER_IDS
    }

    pub fn find(id: &str) -> Option<Box<dyn Provider>> {
        match id {
            "claude" => Some(Box::new(claude::ClaudeProvider)),
            "codex" => Some(Box::new(codex::CodexProvider)),
            "cline" => Some(Box::new(emerging::ClineProvider)),
            "cursor" => Some(Box::new(cursor::CursorProvider)),
            "deepseek" => Some(Box::new(deepseek::DeepseekProvider)),
            "antigravity" => Some(Box::new(emerging::AntigravityProvider)),
            "copilot" => Some(Box::new(emerging::CopilotProvider)),
            "windsurf" => Some(Box::new(emerging::WindsurfProvider)),
            "cidebuddy" => Some(Box::new(emerging::CideBuddyProvider)),
            "codebuddy" => Some(Box::new(emerging::CodeBuddyProvider)),
            "qoder" => Some(Box::new(emerging::QoderProvider)),
            "qwen" => Some(Box::new(emerging::QwenProvider)),
            "trae" => Some(Box::new(emerging::TraeProvider)),
            "trae_gui" | "trae-gui" => Some(Box::new(emerging::TraeGuiProvider)),
            "traecn" | "trae-cn" | "trae_cn" => Some(Box::new(emerging::TraeCnProvider)),
            "droid" | "factory" => Some(Box::new(emerging::DroidProvider)),
            "codybuddycn" | "codybuddy-cn" | "codybuddy_cn" => {
                Some(Box::new(emerging::CodyBuddyCnProvider))
            }
            "stepfun" | "step-fun" | "step_fun" => Some(Box::new(emerging::StepFunProvider)),
            "workbuddy" | "work-buddy" | "work_buddy" => {
                Some(Box::new(emerging::WorkBuddyProvider))
            }
            "hermes" => Some(Box::new(emerging::HermesProvider)),
            "pi" => Some(Box::new(emerging::PiProvider)),
            "omp" | "oh-my-pi" | "oh_my_pi" => Some(Box::new(emerging::OmpProvider)),
            "gemini" => Some(Box::new(gemini::GeminiProvider)),
            "kiro" => Some(Box::new(kiro::KiroProvider)),
            "kimi" => Some(Box::new(kimi::KimiProvider)),
            "opencode" => Some(Box::new(opencode::OpenCodeProvider)),
            _ => None,
        }
    }
}

pub fn all_provider_ids() -> &'static [&'static str] {
    ProviderRegistry::ids()
}

/// Find provider by ID.
pub fn find_provider(id: &str) -> Option<Box<dyn Provider>> {
    ProviderRegistry::find(id)
}

/// Default switch target for a given source provider.
pub fn default_switch_target(from: &str) -> &'static str {
    let ids = all_provider_ids();
    match from {
        "codex" => "claude",
        _ => ids
            .iter()
            .find(|&&id| id != from)
            .copied()
            .unwrap_or("codex"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn registry_exposes_requested_emerging_providers() {
        for id in [
            "gemini",
            "antigravity",
            "cline",
            "copilot",
            "windsurf",
            "cidebuddy",
            "codebuddy",
            "qoder",
            "qwen",
            "trae",
            "trae_gui",
            "traecn",
            "droid",
            "codybuddycn",
            "stepfun",
            "workbuddy",
            "hermes",
            "pi",
            "omp",
        ] {
            assert!(
                all_provider_ids().iter().any(|known| *known == id),
                "missing provider id: {id}"
            );
            assert!(find_provider(id).is_some(), "provider not found: {id}");
        }
    }

    #[test]
    fn traecli_and_trae_gui_have_distinct_display_names() {
        let traecli = find_provider("trae").expect("trae provider");
        let trae_gui = find_provider("trae_gui").expect("trae gui provider");
        assert_eq!(traecli.name(), "TraeCli");
        assert_eq!(trae_gui.name(), "Trae");
    }

    #[test]
    fn factory_alias_resolves_to_droid_provider() {
        let provider = find_provider("factory").expect("factory alias");
        assert_eq!(provider.id(), "droid");
    }
}
