use criterion::{black_box, criterion_group, criterion_main, Criterion};

// Note: benchmarks import the crate's public API.
// Since this is a cdylib (PyO3), we benchmark the internal compute functions
// by re-implementing thin wrappers or using the similarity module directly.
// For now, benchmark string operations that are the main hotspots.

fn generate_pairs(n: usize) -> (Vec<String>, Vec<String>) {
    let left: Vec<String> = (0..n)
        .map(|i| format!("John Smith {}", i % 100))
        .collect();
    let right: Vec<String> = (0..n)
        .map(|i| format!("Jon Smyth {}", i % 100))
        .collect();
    (left, right)
}

fn generate_numeric_pairs(n: usize) -> (Vec<f64>, Vec<f64>) {
    let left: Vec<f64> = (0..n).map(|i| i as f64 * 1.1).collect();
    let right: Vec<f64> = (0..n).map(|i| i as f64 * 1.05 + 0.5).collect();
    (left, right)
}

fn bench_jaro_winkler(c: &mut Criterion) {
    let (left, right) = generate_pairs(10_000);
    c.bench_function("jaro_winkler_10k", |b| {
        b.iter(|| {
            let _: Vec<f64> = left
                .iter()
                .zip(right.iter())
                .map(|(l, r)| strsim::jaro_winkler(black_box(l), black_box(r)))
                .collect();
        })
    });
}

fn bench_soundex(c: &mut Criterion) {
    let (left, right) = generate_pairs(10_000);
    c.bench_function("soundex_10k", |b| {
        b.iter(|| {
            let _: Vec<f64> = left
                .iter()
                .zip(right.iter())
                .map(|(l, r)| {
                    // Inline soundex comparison (can't import from cdylib)
                    fn soundex_code(c: u8) -> u8 {
                        match c {
                            b'B' | b'F' | b'P' | b'V' => b'1',
                            b'C' | b'G' | b'J' | b'K' | b'Q' | b'S' | b'X' | b'Z' => b'2',
                            b'D' | b'T' => b'3',
                            b'L' => b'4',
                            b'M' | b'N' => b'5',
                            b'R' => b'6',
                            _ => b'0',
                        }
                    }
                    fn encode(s: &str) -> [u8; 4] {
                        let mut result = [b'0'; 4];
                        let mut pos = 0usize;
                        let mut last_code = b'0';
                        for &c in s.as_bytes() {
                            if !c.is_ascii_alphabetic() { continue; }
                            let upper = c.to_ascii_uppercase();
                            if pos == 0 {
                                result[0] = upper;
                                pos = 1;
                                last_code = soundex_code(upper);
                                continue;
                            }
                            let code = soundex_code(upper);
                            if code != b'0' && code != last_code {
                                result[pos] = code;
                                pos += 1;
                                if pos == 4 { break; }
                            }
                            last_code = code;
                        }
                        result
                    }
                    if encode(black_box(l)) == encode(black_box(r)) { 1.0 } else { 0.0 }
                })
                .collect();
        })
    });
}

fn bench_numeric_distance(c: &mut Criterion) {
    let (left, right) = generate_numeric_pairs(100_000);
    c.bench_function("numeric_distance_100k", |b| {
        b.iter(|| {
            let _: Vec<f64> = left
                .iter()
                .zip(right.iter())
                .map(|(l, r)| (black_box(l) - black_box(r)).abs())
                .collect();
        })
    });
}

criterion_group!(benches, bench_jaro_winkler, bench_soundex, bench_numeric_distance);
criterion_main!(benches);
