mod common;

use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use oo_ide::utils::fuzzy::filter_and_rank;
use std::path::PathBuf;

// ── Setup functions ───────────────────────────────────────────────────────────
// Run outside the instrumented region; their cost is not counted.

fn monorepo_paths() -> Vec<PathBuf> {
    common::generate_monorepo()
}
fn pathological_paths() -> Vec<PathBuf> {
    common::generate_pathological()
}
fn search_stress_paths() -> Vec<PathBuf> {
    common::generate_search_stress()
}

// ── Load benchmarks ───────────────────────────────────────────────────────────
// Measures the instruction cost of building Vec<PathBuf> from scratch.

#[library_benchmark]
fn load_monorepo() -> Vec<PathBuf> {
    common::generate_monorepo()
}

#[library_benchmark]
fn load_pathological() -> Vec<PathBuf> {
    common::generate_pathological()
}

#[library_benchmark]
fn load_search_stress() -> Vec<PathBuf> {
    common::generate_search_stress()
}

// ── Filter benchmarks ─────────────────────────────────────────────────────────
// Path lists are generated in the setup functions; only filter_and_rank is
// instrumented. Returning .len() prevents dead-code elimination.

// Empty query: all items returned, no scoring work — collection-only path.
#[library_benchmark]
#[bench::monorepo(setup = monorepo_paths)]
#[bench::pathological(setup = pathological_paths)]
#[bench::search_stress(setup = search_stress_paths)]
fn filter_empty(paths: Vec<PathBuf>) -> usize {
    filter_and_rank(&paths, "").len()
}

// Broad match: "file" hits nearly every entry, stressing scoring + sort.
#[library_benchmark]
#[bench::monorepo(setup = monorepo_paths)]
#[bench::pathological(setup = pathological_paths)]
#[bench::search_stress(setup = search_stress_paths)]
fn filter_broad(paths: Vec<PathBuf>) -> usize {
    filter_and_rank(&paths, "file").len()
}

// Medium selectivity: exercises the filename-vs-full-path fallback.
#[library_benchmark]
#[bench::monorepo(setup = monorepo_paths)]
#[bench::pathological(setup = pathological_paths)]
#[bench::search_stress(setup = search_stress_paths)]
fn filter_medium(paths: Vec<PathBuf>) -> usize {
    filter_and_rank(&paths, "component").len()
}

// Multi-token: two independent subsequence scans per entry.
#[library_benchmark]
#[bench::monorepo(setup = monorepo_paths)]
#[bench::pathological(setup = pathological_paths)]
#[bench::search_stress(setup = search_stress_paths)]
fn filter_multi_token(paths: Vec<PathBuf>) -> usize {
    filter_and_rank(&paths, "service validator").len()
}

// No-match: every entry rejected early — worst-case rejection cost.
#[library_benchmark]
#[bench::monorepo(setup = monorepo_paths)]
#[bench::pathological(setup = pathological_paths)]
#[bench::search_stress(setup = search_stress_paths)]
fn filter_no_match(paths: Vec<PathBuf>) -> usize {
    filter_and_rank(&paths, "zzzzzzzzz").len()
}

// ── Typing simulation ─────────────────────────────────────────────────────────
// Runs all prefixes of "component" in a single instrumented call, matching
// the interactive IDE typing loop.

#[library_benchmark]
#[bench::monorepo(setup = monorepo_paths)]
#[bench::pathological(setup = pathological_paths)]
#[bench::search_stress(setup = search_stress_paths)]
fn incremental_typing(paths: Vec<PathBuf>) -> usize {
    let queries = ["c", "co", "com", "comp", "component"];
    let mut last = 0;
    for q in queries {
        last = filter_and_rank(&paths, q).len();
    }
    last
}

library_benchmark_group!(
    name = load_group;
    benchmarks = load_monorepo, load_pathological, load_search_stress
);

library_benchmark_group!(
    name = filter_group;
    benchmarks = filter_empty, filter_broad, filter_medium, filter_multi_token, filter_no_match,
        incremental_typing
);

main!(library_benchmark_groups = load_group, filter_group);