//! Criterion micro-bench for the autocomplete / file-picker fuzzy matcher
//! (`PLAN/08_PERFORMANCE_AND_MEMORY_MODEL.md` §6.5 — "Fuzzy match").
//!
//! `fuzzy_match` / `fuzzy_filter` run on **every keystroke** in the slash /
//! `@file` / path pickers against potentially thousands of candidates, so the
//! scorer must stay sub-millisecond at 10k candidates or the picker stutters
//! under keystroke load. The Indus-tuned weighted scoring (`gapPenalty=3`,
//! `consecutiveReward=4`, `wordBoundaryReward=12`, `lateMatchPenalty=0.15`,
//! `swappedTokenPenalty=6`) is *parity-critical* — it defines the ranking users
//! perceive as correct — so the load-bearing guard is the ranking-order fixture
//! in the crate's unit tests; this bench is the speed companion to it.
//!
//! The bench scores one realistic query against a 10k-path corpus two ways:
//!   - `fuzzy_match` in a tight loop (the raw per-candidate scorer), and
//!   - `fuzzy_filter` (the full multi-token tokenize → score → stable sort path
//!     the picker actually calls — this is what runs on each keystroke).

use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use indusagi_tui::{fuzzy_filter, fuzzy_match};
use std::hint::black_box;

/// Number of candidate paths — the plan's "10k candidate paths" corpus.
const N_CANDIDATES: usize = 10_000;

/// A representative file-tree corpus: nested module paths with varied segment
/// names, the shape a real repo's file picker scans.
fn candidates() -> Vec<String> {
    let dirs = [
        "src", "crates", "tests", "benches", "docs", "scripts", "examples",
    ];
    let mids = [
        "core",
        "runtime",
        "gateway",
        "capabilities",
        "interop",
        "swarm",
        "smithy",
        "tui",
        "render",
        "shell",
        "facade",
        "tracing",
    ];
    let leaves = [
        "mod", "lib", "main", "config", "parser", "handler", "client", "server", "stream",
        "session", "tool", "registry", "adapter", "builder",
    ];
    let mut out = Vec::with_capacity(N_CANDIDATES);
    let mut i = 0usize;
    'fill: loop {
        for d in &dirs {
            for m in &mids {
                for l in &leaves {
                    out.push(format!("{d}/indusagi-{m}/src/{l}_{i}.rs"));
                    i += 1;
                    if out.len() == N_CANDIDATES {
                        break 'fill;
                    }
                }
            }
        }
    }
    out
}

fn bench_fuzzy(c: &mut Criterion) {
    let corpus = candidates();
    // A query that matches a meaningful fraction (subsequence "gateway stream"),
    // so the scorer does real work rather than bailing on the first char.
    let query = "gwstream";

    let mut group = c.benchmark_group("fuzzy_match");
    group.throughput(Throughput::Elements(N_CANDIDATES as u64));

    // The raw per-candidate scorer in a tight loop over the whole corpus.
    group.bench_function("score_10k_candidates", |b| {
        b.iter(|| {
            let mut hits = 0usize;
            for path in &corpus {
                let m = fuzzy_match(black_box(query), black_box(path));
                if m.matches {
                    hits += 1;
                }
            }
            black_box(hits)
        });
    });

    // The full keystroke path: tokenize → per-token score → stable ascending sort.
    group.bench_function("filter_10k_candidates", |b| {
        b.iter(|| {
            let kept = fuzzy_filter(corpus.clone(), black_box(query), |s: &String| s.clone());
            black_box(kept.len())
        });
    });

    group.finish();
}

criterion_group!(benches, bench_fuzzy);
criterion_main!(benches);
