//! ckg — codebase knowledge graph CLI.
//!
//! v0.1 ships 15 subcommands. Many are stubs in early phases and get wired up
//! progressively (see `plans/260507-1432-codebase-knowledge-graph-bootstrap/`).

use std::path::PathBuf;

use anyhow::Result;
use clap::{Parser, Subcommand};

mod commands;

#[derive(Parser, Debug)]
#[command(name = "ckg", version, about = "codebase knowledge graph", long_about = None)]
struct Cli {
    /// Override the base data dir (default: ~/.codebase-kg/).
    #[arg(long, global = true, env = "CKG_HOME")]
    home: Option<PathBuf>,

    #[command(subcommand)]
    cmd: Cmd,
}

#[derive(Subcommand, Debug)]
enum Cmd {
    /// Index a repository into its per-repo Cozo DB.
    Index(commands::index::Args),
    /// List indexed repositories from the registry.
    List(commands::list::Args),
    /// Remove a repository from the registry and delete its DB.
    Remove(commands::remove::Args),
    /// Print stats for the current (or specified) repo.
    Stats(commands::stats::Args),
    /// Look up a symbol by partial name.
    Symbol(commands::symbol::Args),
    /// Upward transitive closure ("if I change X, what breaks?").
    BlastRadius(commands::blast_radius::Args),
    /// Forward call traversal from a function.
    CallChain(commands::call_chain::Args),
    /// BM25 / semantic search across symbols.
    Search(commands::search::Args),
    /// Map a GitHub PR diff to enclosing symbols, with route/test/caller impact.
    ChangedSymbols(commands::changed_symbols::Args),
    /// Symbols with no inbound CALLS edges.
    DeadCode(commands::dead_code::Args),
    /// Calls whose target doesn't resolve to any Symbol — finds
    /// references to deleted / renamed symbols (e.g. a spec that calls
    /// a method since removed from the service it tests).
    OrphanCalls(commands::orphan_calls::Args),
    /// Edges that cross repository boundaries (registry-resolved).
    CrossRepoEdges(commands::cross_repo_edges::Args),
    /// HTTP routes extracted from frameworks.
    Routes(commands::routes::Args),
    /// Traced execution flows from entry points.
    Processes(commands::processes::Args),
    /// Run an arbitrary Datalog query.
    Query(commands::query::Args),
    /// Export the graph (json | dot | ai-context).
    Export(commands::export::Args),
    /// Run as MCP server over stdio.
    Mcp(commands::mcp::Args),
    /// First-party callers of vendored / third-party (`@external`) symbols. Inverse of `--include-external`. Use `--deprecated` to narrow to callees tagged `@deprecated`.
    VendorUses(commands::vendor_uses::Args),
    /// Shortcut for `ckg vendor-uses --all --deprecated`: every first-party call site reaching a deprecated vendored symbol.
    DeprecatedDeps(commands::vendor_uses::DeprecatedDepsArgs),
    /// Install SCIP indexer binaries into `$CKG_HOME/bin/`. Platform: macOS / Linux only.
    ///
    ///rust — `rustup component add rust-analyzer`. Bootstraps the rust toolchain via Homebrew when missing. Skipped when `rust-analyzer` already on $PATH.
    ///
    ///ruby — `gem install scip-ruby`. Bootstraps Ruby via Homebrew when missing.
    ///
    ///python — `npm install @sourcegraph/scip-python`. Bootstraps Node.js via Homebrew when missing.
    ///
    ///typescript — `npm install @sourcegraph/scip-typescript`. Same Node.js bootstrap as scip-python.
    ///
    ///Bootstrap is interactive on a TTY; pass `--yes` (`-y`) to auto-confirm.
    ///
    ///Prefer `ckg setup` to install all four at once. Use this command for fine-grained control: `ckg install-scip rust ruby`.
    InstallScip(commands::install_scip::Args),
    /// One-shot post-install: prepare `$CKG_HOME/`, install all SCIP indexers, AND warm the embedding-model cache. Run once after `cargo install ckg-cli`. Idempotent. Platform: macOS / Linux only.
    ///
    /// Missing host runtimes (rust toolchain, Node.js, Ruby) are bootstrapped via Homebrew. On a TTY you'll be prompted; pass `--yes` (`-y`) to auto-confirm. If `brew` is missing, setup hard-fails with the install URL.
    ///
    /// scip-rust — rust toolchain via `brew install rustup-init`.
    ///
    /// scip-ruby — Ruby via `brew install ruby`.
    ///
    /// scip-python / scip-typescript — Node.js via `brew install node`.
    ///
    /// Embedding model: nomic-embed-text-v1.5 (~150 MB ONNX) downloads to `$CKG_HOME/models/` so the first `ckg index` doesn't pay the cost. Skip with `--no-embed` or set `CKG_OFFLINE=1` to refuse downloads.
    ///
    /// Restrict scope: `ckg setup --only rust ruby` (SCIP indexers); `ckg setup --no-embed` (skip model preload).
    Setup(commands::setup::Args),
}

fn main() -> Result<()> {
    init_tracing();
    let cli = Cli::parse();

    match cli.cmd {
        Cmd::Index(a) => commands::index::run(cli.home, a),
        Cmd::List(a) => commands::list::run(cli.home, a),
        Cmd::Remove(a) => commands::remove::run(cli.home, a),
        Cmd::Stats(a) => commands::stats::run(cli.home, a),
        Cmd::Symbol(a) => commands::symbol::run(cli.home, a),
        Cmd::BlastRadius(a) => commands::blast_radius::run(cli.home, a),
        Cmd::CallChain(a) => commands::call_chain::run(cli.home, a),
        Cmd::Search(a) => commands::search::run(cli.home, a),
        Cmd::ChangedSymbols(a) => commands::changed_symbols::run(cli.home, a),
        Cmd::DeadCode(a) => commands::dead_code::run(cli.home, a),
        Cmd::OrphanCalls(a) => commands::orphan_calls::run(cli.home, a),
        Cmd::CrossRepoEdges(a) => commands::cross_repo_edges::run(cli.home, a),
        Cmd::Routes(a) => commands::routes::run(cli.home, a),
        Cmd::Processes(a) => commands::processes::run(cli.home, a),
        Cmd::Query(a) => commands::query::run(cli.home, a),
        Cmd::Export(a) => commands::export::run(cli.home, a),
        Cmd::Mcp(a) => commands::mcp::run(cli.home, a),
        Cmd::InstallScip(a) => commands::install_scip::run(cli.home, a),
        Cmd::Setup(a) => commands::setup::run(cli.home, a),
        Cmd::VendorUses(a) => commands::vendor_uses::run(cli.home, a),
        Cmd::DeprecatedDeps(a) => commands::vendor_uses::run_deprecated_deps(cli.home, a),
    }
}

fn init_tracing() {
    let _ = tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .with_writer(std::io::stderr)
        .try_init();
}

/// Resolve the ckg base directory: explicit > env > default.
pub(crate) fn ckg_home(explicit: Option<PathBuf>) -> Result<PathBuf> {
    if let Some(p) = explicit {
        return Ok(p);
    }
    let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("no home dir"))?;
    Ok(home.join(".codebase-kg"))
}
