//! First-run selector (decision #7). Always asks — never detect-and-defaults.
//! Validates the choice (lists models / checks the CLI) and stores any pasted
//! key via the secret backend before persisting the non-secret selection.
//!
//! The UI is themed via a global inquire `RenderConfig` for a consistent look;
//! provider rows carry at-a-glance availability hints (informational only — the
//! user still chooses), and model lists are filterable.

use crate::config::{
    self, Config, Overrides, PartialConfig, Paths, ProviderId, DEFAULT_OLLAMA_URL,
};
use crate::providers::{
    binary_on_path, AnthropicProvider, ClaudeProvider, CodexProvider, Flavor, OpenAiCompat,
    Provider,
};
use crate::secret;
use anyhow::{anyhow, Result};
use inquire::{Confirm, Password, PasswordDisplayMode, Select, Text};
use owo_colors::OwoColorize;

/// A provider row in the selector: aligned id + kind + availability hint.
struct ProviderChoice {
    id: ProviderId,
    label: String,
}

impl std::fmt::Display for ProviderChoice {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.label)
    }
}

/// Run the interactive selector and return a resolved config.
pub async fn run_wizard(
    paths: &Paths,
    ov: &Overrides,
    preselect: Option<ProviderId>,
) -> Result<Config> {
    apply_theme();

    println!();
    println!("  {}", "hephaes setup".bold());
    println!(
        "  {}",
        "Pick a model backend. You can change it later with `hephaes login`.".dimmed()
    );
    println!();

    let provider = match preselect {
        Some(p) => p,
        None => {
            let options: Vec<ProviderChoice> = ProviderId::all()
                .into_iter()
                .map(|id| ProviderChoice {
                    id,
                    label: provider_label(id, paths),
                })
                .collect();
            Select::new("Provider", options)
                .with_help_message("↑↓ move · type to filter · enter to select")
                .with_page_size(6)
                .prompt()?
                .id
        }
    };

    println!();

    // Per-provider auth step.
    let mut ollama_url: Option<String> = None;
    match provider {
        ProviderId::Claude | ProviderId::Codex => {
            if binary_on_path(provider.as_str()) {
                println!(
                    "  {} using your {} login via the `{}` CLI",
                    "•".cyan(),
                    provider.as_str(),
                    provider.as_str()
                );
            } else {
                println!(
                    "  {} `{}` is not on PATH — install it and run `{} login` first",
                    "!".yellow(),
                    provider.as_str(),
                    provider.as_str()
                );
            }
        }
        ProviderId::Ollama => {
            let url = Text::new("Ollama server URL")
                .with_default(DEFAULT_OLLAMA_URL)
                .with_help_message("press enter to accept the default")
                .prompt()?;
            ollama_url = Some(url);
        }
        ProviderId::Anthropic | ProviderId::Openai | ProviderId::Openrouter => {
            let reuse = secret::get_key(paths, provider).is_some()
                && Confirm::new("A key is already stored. Use it?")
                    .with_default(true)
                    .prompt()
                    .unwrap_or(true);
            if !reuse {
                let key = Password::new(&format!("{} API key", provider.as_str()))
                    .without_confirmation()
                    .with_display_mode(PasswordDisplayMode::Masked)
                    .with_help_message("input hidden · paste, then press enter")
                    .prompt()?;
                secret::set_key(paths, provider, key.trim())?;
                println!(
                    "  {} key saved to {}",
                    "•".cyan(),
                    secret::backend_name().dimmed()
                );
            }
        }
    }

    println!();

    // Model selection (also validates the key / URL where possible).
    let model = select_model(provider, paths, ollama_url.as_deref()).await?;

    // Persist non-secret selection.
    let partial = PartialConfig {
        provider: Some(provider),
        model: model.clone(),
        ollama_url: ollama_url.clone(),
        wiki_dir: ov.wiki_dir.clone(),
        ..Default::default()
    };
    config::save_partial(paths, &partial)?;

    print_summary(provider, model.as_deref());
    config::resolve(paths, ov)?.ok_or_else(|| anyhow!("failed to load saved config"))
}

fn print_summary(provider: ProviderId, model: Option<&str>) {
    println!();
    println!("  {}  {}", "✓".green().bold(), "ready".green().bold());
    println!("    {} {}", "provider".dimmed(), provider.as_str());
    println!(
        "    {}    {}",
        "model".dimmed(),
        model.unwrap_or("provider default")
    );
    println!();
}

/// `id   kind                    hint` — plain text so column widths are exact.
fn provider_label(id: ProviderId, paths: &Paths) -> String {
    let kind = match id {
        ProviderId::Claude => "subscription · claude CLI",
        ProviderId::Codex => "subscription · codex CLI",
        ProviderId::Anthropic => "API key",
        ProviderId::Openai => "API key",
        ProviderId::Openrouter => "API key",
        ProviderId::Ollama => "local server",
    };
    let hint = match id {
        ProviderId::Claude | ProviderId::Codex => {
            if binary_on_path(id.as_str()) {
                "ready"
            } else {
                "CLI not found"
            }
        }
        ProviderId::Ollama => "local",
        _ => {
            if secret::get_key(paths, id).is_some() {
                "key set"
            } else {
                "needs key"
            }
        }
    };
    format!("{:<11} {:<26} {}", id.as_str(), kind, hint)
}

async fn select_model(
    provider: ProviderId,
    paths: &Paths,
    ollama_url: Option<&str>,
) -> Result<Option<String>> {
    let p: Box<dyn Provider> = match provider {
        ProviderId::Claude => Box::new(ClaudeProvider::new(None)),
        ProviderId::Codex => Box::new(CodexProvider::new(None)),
        ProviderId::Anthropic => Box::new(AnthropicProvider::new(
            secret::get_key(paths, provider),
            None,
        )),
        ProviderId::Openai => Box::new(OpenAiCompat::new(
            Flavor::OpenAi,
            "https://api.openai.com/v1".to_string(),
            secret::get_key(paths, provider),
            None,
        )),
        ProviderId::Openrouter => Box::new(OpenAiCompat::new(
            Flavor::OpenRouter,
            "https://openrouter.ai/api/v1".to_string(),
            secret::get_key(paths, provider),
            None,
        )),
        ProviderId::Ollama => {
            let base = format!(
                "{}/v1",
                ollama_url
                    .unwrap_or(DEFAULT_OLLAMA_URL)
                    .trim_end_matches('/')
            );
            Box::new(OpenAiCompat::new(Flavor::Ollama, base, None, None))
        }
    };

    let mut ids = match p.list_models().await {
        Ok(v) => v,
        Err(e) => {
            println!(
                "  {} couldn't list models ({}); enter one manually",
                "!".yellow(),
                crate::util::truncate_chars(&format!("{e:#}"), 80).dimmed()
            );
            Vec::new()
        }
    };
    ids.sort();
    pick_model(ids, provider.is_native_agent())
}

const DEFAULT_LABEL: &str = "▸ provider/CLI default";

fn pick_model(ids: Vec<String>, allow_default: bool) -> Result<Option<String>> {
    let mut opts: Vec<String> = Vec::new();
    if allow_default {
        opts.push(DEFAULT_LABEL.to_string());
    }
    opts.extend(ids);

    if opts.is_empty() {
        let m = Text::new("Model id").prompt()?;
        let m = m.trim().to_string();
        return Ok(if m.is_empty() { None } else { Some(m) });
    }

    let choice = Select::new("Model", opts)
        .with_help_message("type to filter · ↑↓ move · enter to select")
        .with_page_size(12)
        .prompt()?;
    if choice == DEFAULT_LABEL {
        Ok(None)
    } else {
        Ok(Some(choice))
    }
}

/// Theme all inquire prompts for a consistent, clean look.
fn apply_theme() {
    use inquire::ui::{Attributes, Color, RenderConfig, StyleSheet, Styled};
    let rc = RenderConfig::default()
        .with_prompt_prefix(Styled::new("›").with_fg(Color::LightCyan))
        .with_answered_prompt_prefix(Styled::new("✓").with_fg(Color::LightGreen))
        .with_highlighted_option_prefix(Styled::new("❯").with_fg(Color::LightCyan))
        .with_selected_option(Some(
            StyleSheet::new()
                .with_fg(Color::LightCyan)
                .with_attr(Attributes::BOLD),
        ))
        .with_help_message(StyleSheet::new().with_fg(Color::DarkGrey));
    inquire::set_global_render_config(rc);
}
