//! Static curated model catalogues for structured Claude/Codex profiles.
//!
//! The CLIs do not expose a stable machine-readable model catalogue. These lists
//! are therefore intentionally small, static and infallible; the UI must still
//! keep manual entry as a fallback for models not listed here.

use domain::profile::StructuredAdapter;

/// One searchable model entry for a structured profile adapter.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProfileModelCatalogEntry {
    /// Structured adapter this model belongs to.
    pub adapter: StructuredAdapter,
    /// Exact model identifier to persist on [`domain::profile::AgentProfile::model`].
    pub model_id: String,
    /// Human-readable label for picker display.
    pub display_name: String,
    /// Extra search tokens useful to the frontend.
    pub aliases: Vec<String>,
    /// Whether this entry is the conservative default suggestion.
    pub recommended: bool,
}

fn entry(
    adapter: StructuredAdapter,
    model_id: &str,
    display_name: &str,
    aliases: &[&str],
    recommended: bool,
) -> ProfileModelCatalogEntry {
    ProfileModelCatalogEntry {
        adapter,
        model_id: model_id.to_owned(),
        display_name: display_name.to_owned(),
        aliases: aliases.iter().map(|alias| (*alias).to_owned()).collect(),
        recommended,
    }
}

/// Static Claude Code model catalogue.
#[must_use]
pub fn claude_model_catalogue() -> Vec<ProfileModelCatalogEntry> {
    vec![
        entry(
            StructuredAdapter::Claude,
            "claude-sonnet-5",
            "Claude Sonnet 5",
            &["sonnet"],
            true,
        ),
        entry(
            StructuredAdapter::Claude,
            "claude-opus-4-8",
            "Claude Opus 4.8",
            &["opus"],
            false,
        ),
        entry(
            StructuredAdapter::Claude,
            "claude-haiku-4-5-20251001",
            "Claude Haiku 4.5",
            &["haiku"],
            false,
        ),
    ]
}

/// Static OpenAI Codex CLI model catalogue.
#[must_use]
pub fn codex_model_catalogue() -> Vec<ProfileModelCatalogEntry> {
    vec![
        entry(
            StructuredAdapter::Codex,
            "gpt-5-codex",
            "GPT-5 Codex",
            &["codex"],
            true,
        ),
        entry(
            StructuredAdapter::Codex,
            "gpt-5",
            "GPT-5",
            &["general"],
            false,
        ),
        entry(
            StructuredAdapter::Codex,
            "gpt-5-mini",
            "GPT-5 mini",
            &["mini", "fast"],
            false,
        ),
    ]
}

/// Use case exposing the static Claude model catalogue.
pub struct ListClaudeModels;

/// Output of [`ListClaudeModels::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListClaudeModelsOutput {
    /// The catalogue entries.
    pub models: Vec<ProfileModelCatalogEntry>,
}

impl ListClaudeModels {
    /// Builds the use case (stateless, no ports to inject).
    #[must_use]
    pub const fn new() -> Self {
        Self
    }

    /// Lists curated Claude models. Infallible.
    #[must_use]
    pub fn execute(&self) -> ListClaudeModelsOutput {
        ListClaudeModelsOutput {
            models: claude_model_catalogue(),
        }
    }
}

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

/// Use case exposing the static Codex model catalogue.
pub struct ListCodexModels;

/// Output of [`ListCodexModels::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListCodexModelsOutput {
    /// The catalogue entries.
    pub models: Vec<ProfileModelCatalogEntry>,
}

impl ListCodexModels {
    /// Builds the use case (stateless, no ports to inject).
    #[must_use]
    pub const fn new() -> Self {
        Self
    }

    /// Lists curated Codex models. Infallible.
    #[must_use]
    pub fn execute(&self) -> ListCodexModelsOutput {
        ListCodexModelsOutput {
            models: codex_model_catalogue(),
        }
    }
}

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

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

    #[test]
    fn static_catalogues_are_non_empty_searchable_and_have_one_default() {
        for (adapter, models) in [
            (StructuredAdapter::Claude, claude_model_catalogue()),
            (StructuredAdapter::Codex, codex_model_catalogue()),
        ] {
            assert!(!models.is_empty());
            assert_eq!(
                models.iter().filter(|model| model.recommended).count(),
                1,
                "{adapter:?} should expose one default suggestion"
            );
            for model in models {
                assert_eq!(model.adapter, adapter);
                assert!(!model.model_id.trim().is_empty());
                assert!(!model.display_name.trim().is_empty());
            }
        }
    }
}
