//! `vex check` — fast symbol-existence probe via the FST.
//! Extracted from `cli/mod.rs` in S1 Group B.2.
//!
//! v1.12.0 T4 — when the bloom sidecar (`index.bloom`) is present, each
//! name is pre-filtered through it: a `may_contain == false` short-
//! circuits the FST lookup to `(name, false)`. A missing or corrupt
//! sidecar is non-fatal; we just skip the optimisation and fall
//! through to the FST as before.

use anyhow::{Context, Result};

use super::args::OutputFormat;
use super::common::{resolve_root, CmdCtx};
use super::index_management::ensure_index_ready;
use super::output::print_envelope;
use crate::protocol::capabilities;
use crate::search::bloom::SymbolBloom;
use crate::store::reader::IndexReader;
use crate::util::config;

pub(crate) fn check(
    ctx: &CmdCtx<'_>,
    names: Vec<String>,
    path: Option<std::path::PathBuf>,
    auto_update: bool,
    no_stale_check: bool,
) -> Result<()> {
    let root = resolve_root(path)?.canonicalize()?;
    let index_path = ensure_index_ready(
        &root,
        auto_update,
        no_stale_check,
        false,
        ctx.local_cache_active,
        ctx.cfg,
    )?;

    let reader = IndexReader::open(&index_path).context("open index")?;

    // Load bloom sidecar if present. `Err` (corruption) is treated the
    // same as `Ok(None)`: silently degrade to direct FST lookups —
    // bloom is an optimisation and a corrupt sidecar must not wedge
    // `vex check`.
    let bloom_path = config::bloom_path(&root);
    let bloom = match SymbolBloom::load(&bloom_path) {
        Ok(b) => b,
        Err(e) => {
            tracing::debug!(
                path = %bloom_path.display(),
                error = %e,
                "bloom sidecar unreadable; falling back to direct FST"
            );
            None
        }
    };

    // Case-insensitive exact match: FST candidates filtered by actual
    // name. Pre-filtered by bloom (lowercased query) when the sidecar
    // is loaded — a `may_contain == false` is a guaranteed miss.
    let results: Vec<(String, bool)> = if let Some(fst) = reader.symbol_fst_reader() {
        names
            .iter()
            .map(|n| {
                let lower = n.to_lowercase();
                if let Some(b) = bloom.as_ref() {
                    if !b.may_contain(&lower) {
                        return (n.clone(), false);
                    }
                }
                let found = fst.find(n).iter().any(|&idx| {
                    reader
                        .symbol(idx as usize)
                        .map(|r| reader.read_string(r.name_offset).to_lowercase() == lower)
                        .unwrap_or(false)
                });
                (n.clone(), found)
            })
            .collect()
    } else {
        // Fallback: build lowercased set for consistent case-insensitive matching
        let all_lower: std::collections::HashSet<String> = (0..reader.symbol_count())
            .filter_map(|i| {
                let rec = reader.symbol(i)?;
                let name = reader.read_string(rec.name_offset);
                if name.is_empty() {
                    None
                } else {
                    Some(name.to_lowercase())
                }
            })
            .collect();
        names
            .iter()
            .map(|n| {
                let lower = n.to_lowercase();
                if let Some(b) = bloom.as_ref() {
                    if !b.may_contain(&lower) {
                        return (n.clone(), false);
                    }
                }
                (n.clone(), all_lower.contains(&lower))
            })
            .collect()
    };

    match ctx.format {
        OutputFormat::Json => {
            let json: serde_json::Value = results
                .iter()
                .map(|(name, found)| serde_json::json!({ "name": name, "exists": found }))
                .collect();
            print_envelope(
                &json,
                capabilities::current(),
                super::output::default_meta_for(&root),
            );
        }
        OutputFormat::Text | OutputFormat::Compact => {
            for (name, found) in &results {
                let mark = if *found { "+" } else { "-" };
                println!("{mark} {name}");
            }
        }
    }
    Ok(())
}
