//! Criterion microbench: per-class coverage of the `inkferro-rt` line-diff
//! transport (`LineDiff::diff`).
//!
//! Complements `benches/diff.rs` (bench id `rt_diff`, the 5x-gate-paired
//! pipeline-golden small delta) with representative frame classes:
//!
//! - `rt_diff_small_delta_50x80`: 50-line frame, 1 interior line changed
//! - `rt_diff_full_change_50x80`: 50-line frame, every line changed
//! - `rt_diff_noop_50x80`: byte-identical frame (zero-byte skip path)
//! - `rt_diff_tall_200x50`: 200-line x 50-col frame, 1 interior line changed
//! - `rt_diff_shrink_50_to_25`: line count shrinks 50 -> 25
//! - `rt_diff_grow_25_to_50`: line count grows 25 -> 50
//!
//! Each iteration measures a primed steady-state `diff` (baseline set via the
//! byte-free `sync`), mirroring `benches/diff.rs`.

use std::hint::black_box;

use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use inkferro_rt::LineDiff;

/// Deterministic synthetic frame: `rows` lines, each exactly `cols` chars,
/// trailing newline (the usual ink frame shape).
fn frame(rows: usize, cols: usize, tag: &str) -> String {
    let mut s = String::new();
    for r in 0..rows {
        let head = format!("{tag} row {r:04} ");
        s.push_str(&head);
        for c in head.len()..cols {
            s.push(if (r + c) % 7 == 0 { '.' } else { 'x' });
        }
        s.push('\n');
    }
    s
}

/// Replace one interior line of `prev` (row `row`) with a same-width variant.
fn with_one_line_changed(prev: &str, row: usize) -> String {
    let mut lines: Vec<String> = prev.lines().map(str::to_owned).collect();
    let target = &mut lines[row];
    *target = target.replace('x', "y");
    let mut out = lines.join("\n");
    out.push('\n');
    out
}

fn bench_case(c: &mut Criterion, id: &str, previous: &str, next: &str) {
    c.bench_function(id, |b| {
        b.iter_batched(
            || {
                let mut transport = LineDiff::new();
                transport.sync(previous);
                transport
            },
            |mut transport| black_box(transport.diff(black_box(next))),
            BatchSize::SmallInput,
        );
    });
}

fn bench_rt_diff_classes(c: &mut Criterion) {
    // small delta: 1 line of a 50-line frame changed
    let prev_50 = frame(50, 80, "alpha");
    let small_next = with_one_line_changed(&prev_50, 25);
    assert_ne!(prev_50, small_next);
    bench_case(c, "rt_diff_small_delta_50x80", &prev_50, &small_next);

    // full-frame change: every line differs
    let full_next = frame(50, 80, "bravo");
    assert_ne!(prev_50, full_next);
    bench_case(c, "rt_diff_full_change_50x80", &prev_50, &full_next);

    // no-op frame: byte-identical, exercises the zero-byte skip
    let noop_next = prev_50.clone();
    bench_case(c, "rt_diff_noop_50x80", &prev_50, &noop_next);

    // tall frame: 200 rows x 50 cols, 1 interior line changed
    let prev_tall = frame(200, 50, "tall");
    let tall_next = with_one_line_changed(&prev_tall, 100);
    assert_ne!(prev_tall, tall_next);
    bench_case(c, "rt_diff_tall_200x50", &prev_tall, &tall_next);

    // line-count shrink: 50 -> 25
    let prev_25 = frame(25, 80, "alpha");
    bench_case(c, "rt_diff_shrink_50_to_25", &prev_50, &prev_25);

    // line-count grow: 25 -> 50
    bench_case(c, "rt_diff_grow_25_to_50", &prev_25, &prev_50);
}

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