//! `ckg setup` — one-shot post-install: prepare `$CKG_HOME/`, install all
//! SCIP indexers (rust, ruby, python, typescript), warm the embedding
//! model cache, and print a summary.
//!
//! Intended to be the first thing a user runs after `cargo install ckg-cli`.
//! Idempotent — re-running skips anything already in place. No prompts;
//! every step is silent unless something fails.

use std::path::PathBuf;

use anyhow::Result;
use clap::Args as ClapArgs;

// Long help (platform / prerequisites / examples) lives on the
// `Cmd::Setup` variant in `main.rs` — clap derive reads `about` /
// `long_about` from there, not from the `Args` struct.

#[derive(ClapArgs, Debug)]
pub struct Args {
    /// Restrict to specific SCIP indexers (default: all four — rust ruby python typescript).
    #[arg(long, value_name = "LANG", num_args = 1..)]
    pub only: Vec<String>,
    /// Skip the embedding-model preload. Use when you'll only run
    /// `ckg index --no-embed` and don't want to pay the ~150 MB download.
    #[arg(long)]
    pub no_embed: bool,
    /// Auto-confirm installation of missing host runtimes via the system
    /// package manager (brew / scoop / winget). Skips the interactive prompt.
    #[arg(long, short = 'y')]
    pub yes: bool,
}

pub fn run(home: Option<PathBuf>, args: Args) -> Result<()> {
    let base = crate::ckg_home(home.clone())?;
    eprintln!("ckg setup — preparing {}", base.display());

    // Ensure base + bin/ exist (install-scip also does this, but we want
    // the directories present even if the user only restricts the lang
    // list to one indexer that's already installed).
    // create_dir_secure (N1): set mode 0700 atomically at create time
    // for fresh dirs so a same-host attacker can't slip files in
    // through the create→chmod TOCTOU window. workspace_folders and
    // registry don't need 0700 (they're per-repo Cozo DBs that may need
    // wider read for tooling), so plain create_dir_all is fine there.
    ckg_fs::create_dir_secure(&base)?;
    ckg_fs::create_dir_secure(&base.join("bin"))?;
    ckg_fs::create_dir_secure(&base.join("models"))?;
    // L5: workspace_folders and registry are readable by group tooling
    // (IDE extensions, read-only diagnostics). Using plain create_dir_all
    // leaves the mode at the process umask (typically 0755). A future
    // `ckg_fs::create_dir_with_mode(path, 0o750)` helper would let us
    // tighten these to group-only, blocking world-read. Tracked for the
    // next ckg-fs API addition.
    std::fs::create_dir_all(base.join("workspace_folders"))?;
    std::fs::create_dir_all(base.join("registry"))?;

    // Run install-scip with the same arg shape — empty `langs` means
    // "all four", which is the default `ckg setup` behavior.
    let scip_args = super::install_scip::Args {
        langs: args.only.clone(),
        yes: args.yes,
    };
    super::install_scip::run(home, scip_args)?;

    // Warm the embedding-model cache so the first `ckg index` doesn't
    // pay the ~150 MB download. Skipped under `--no-embed` or when the
    // user has opted into offline mode (CKG_OFFLINE=1) without a cache.
    let models_dir = base.join("models");
    let embed_done = if args.no_embed {
        eprintln!("\nembed: skipped (--no-embed)");
        false
    } else if std::env::var_os("CKG_OFFLINE").is_some() && !has_cached_model(&models_dir) {
        eprintln!("\nembed: skipped (CKG_OFFLINE=1 set; no cached model)");
        false
    } else {
        eprintln!(
            "\nembed: preloading nomic-embed-text-v1.5 (~150 MB) → {}",
            models_dir.display()
        );
        match preload_embedder(&models_dir) {
            Ok(()) => {
                eprintln!("embed: ready");
                true
            }
            Err(e) => {
                // Non-fatal — `ckg index --no-embed` still works, and
                // the model can be fetched lazily on first real use.
                eprintln!("embed: preload failed (non-fatal): {e}");
                false
            }
        }
    };

    eprintln!("\nckg setup: done.");
    eprintln!(
        "  • DB root:    {}",
        base.join("workspace_folders").display()
    );
    eprintln!("  • Indexers:   {}", base.join("bin").display());
    if embed_done {
        eprintln!("  • Models:     {}", models_dir.display());
    }
    eprintln!("\nNext: `ckg index <path>` to scan a repo.");
    Ok(())
}

/// Instantiate `Embedder` against the given cache dir and embed a single
/// short string — that triggers fastembed's lazy-init path which downloads
/// the ONNX weights to `cache_dir`. Subsequent `ckg index` runs reuse them.
fn preload_embedder(cache_dir: &std::path::Path) -> Result<()> {
    let embedder = ckg_embed::Embedder::with_cache_dir(cache_dir.to_path_buf());
    embedder
        .embed_one("warmup")
        .map_err(|e| anyhow::anyhow!("embed warmup: {e}"))?;
    Ok(())
}

/// True if `cache_dir` holds at least one real ONNX artifact (recursive
/// scan). Avoids false positives from `.DS_Store`, partial / truncated
/// downloads (require >= 1 MiB), or zero-byte sentinels.
fn has_cached_model(cache_dir: &std::path::Path) -> bool {
    const MIN_ONNX_BYTES: u64 = 1024 * 1024; // 1 MiB
    ckg_embed::has_cached_model(cache_dir, MIN_ONNX_BYTES)
}
