//! `ckg blast-radius <symbol>` — upward transitive closure over Calls.
use std::collections::BTreeMap;
use std::path::PathBuf;

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

#[derive(ClapArgs, Debug)]
pub struct Args {
    pub name: String,
    #[arg(long, default_value_t = 2)]
    pub depth: u32,
    #[arg(long)]
    pub with_recency: bool,
    /// Include `@external` symbols (third-party / vendored callers).
    #[arg(long)]
    pub include_external: bool,
}

pub fn run(home: Option<PathBuf>, args: Args) -> Result<()> {
    let _ = args.with_recency; // recency weighting deferred
    let (_base, storage) = super::repo_lookup::open_for(home, None)?;
    // Resolve symbol id from partial qname.
    let mut p = BTreeMap::new();
    p.insert("q".into(), DataValue::from(args.name.as_str()));
    let seeds =
        storage.run_with_immutable("?[id] := *Symbol{id, qname}, str_includes(qname, $q)", p)?;
    if seeds.rows.is_empty() {
        println!("(no symbol matched {:?})", args.name);
        return Ok(());
    }
    let seed_id = match &seeds.rows[0][0] {
        DataValue::Str(s) => s.to_string(),
        _ => return Ok(()),
    };
    let (script, params) = ckg_storage::queries::blast_radius(&seed_id, args.depth);
    let rows = storage.run_with_immutable(&script, params)?;
    // Post-filter @external rows unless opted in.
    let external_ids = if !args.include_external {
        load_external_ids(&storage)?
    } else {
        std::collections::HashSet::new()
    };
    let filtered: Vec<_> = rows
        .rows
        .iter()
        .filter(|r| match r.first() {
            Some(DataValue::Str(id)) => !external_ids.contains(id.as_str()),
            _ => true,
        })
        .collect();
    println!("blast radius of {seed_id}: {} symbols", filtered.len());
    for row in filtered.iter().take(50) {
        println!("  {:?}", row[0]);
    }
    Ok(())
}

/// Read every Symbol's id where `doc` carries `@external`. Used to
/// post-filter result sets. Loaded lazily — callers pay only when
/// `--include-external` isn't set.
fn load_external_ids(
    storage: &ckg_storage::Storage,
) -> Result<std::collections::HashSet<String>> {
    let rows = storage
        .run_immutable("?[id] := *Symbol{id, doc}, str_includes(doc, \"@external\")")?;
    Ok(rows
        .rows
        .into_iter()
        .filter_map(|r| match r.into_iter().next() {
            Some(DataValue::Str(s)) => Some(s.to_string()),
            _ => None,
        })
        .collect())
}
