//! Stage 2 scheduler timing tests — deterministic, no hardware. Asserts the click
//! timeline math (subdivision spacing, swing ratio, polymeter, accents, mute).

use track_format::parse;
use track_format::schedule::{lane_durs, master_bar_ns, render};

const S: i64 = 1_000_000_000; // one second in ns

#[test]
fn quarter_notes_at_120() {
    let t = parse("t120;kick:4=Xxxx");
    let cl = render(&t, 1);
    assert_eq!(cl.iter().map(|c| c.time_ns).collect::<Vec<_>>(), vec![0, S / 2, S, 3 * S / 2]);
    assert_eq!(cl.iter().map(|c| c.level).collect::<Vec<_>>(), vec![2, 1, 1, 1]); // accent on beat 1
}

#[test]
fn eighth_subdivision_spacing() {
    // hats on the quarters only (X.x.x.x.) over an 8th grid → 0, 0.5, 1.0, 1.5 s
    let t = parse("t120;hatClosed:4/2=X.x.x.x.");
    let cl = render(&t, 1);
    assert_eq!(cl.iter().map(|c| c.time_ns).collect::<Vec<_>>(), vec![0, S / 2, S, 3 * S / 2]);
    assert_eq!(cl[0].level, 2);
}

#[test]
fn swing_splits_two_thirds_one_third() {
    let t = parse("t120;ride:4/2s=Xxxxxxxx"); // all 8ths sounding, swung
    let durs = lane_durs(&t.lanes[0], 120, master_bar_ns(&t));
    let beat = 500_000_000;
    assert_eq!(durs[0], beat * 2 / 3); // on-beat = long
    assert_eq!(durs[1], beat / 3); // off-beat = short
    assert!(durs[0] > durs[1]);
    // a long+short pair is one beat (within integer-division rounding)
    assert!((durs[0] + durs[1] - beat).abs() <= 1);
}

#[test]
fn polymeter_five_over_four() {
    let t = parse("t100;kick:4=Xxxx;claves:5=Xxxxx~");
    let mbar = master_bar_ns(&t);
    assert_eq!(mbar, 2_400_000_000); // 4 beats at 100 bpm

    // claves (lane 1) is poly: 5 steps spread evenly across the master bar
    assert_eq!(lane_durs(&t.lanes[1], 100, mbar), vec![480_000_000; 5]);

    let claves: Vec<i64> = render(&t, 1).into_iter().filter(|c| c.lane == 1).map(|c| c.time_ns).collect();
    assert_eq!(claves, vec![0, 480_000_000, 960_000_000, 1_440_000_000, 1_920_000_000]);
}

#[test]
fn accents_and_ghosts_carry_through() {
    let t = parse("t120;snare:4/3=..gg.gX.gg.g"); // ghosts (3) + an accent (2)
    let cl = render(&t, 1);
    assert!(cl.iter().any(|c| c.level == 3)); // ghost present
    assert!(cl.iter().any(|c| c.level == 2)); // accent present
}

#[test]
fn muted_lane_is_silent() {
    let t = parse("t120;kick:4=Xxxx;snare:4=Xxxx!");
    let cl = render(&t, 1);
    assert!(cl.iter().all(|c| c.lane == 0)); // the muted snare produces no clicks
}

#[test]
fn loops_over_multiple_bars() {
    let t = parse("t120;kick:4=Xxxx");
    assert_eq!(render(&t, 2).len(), 8); // 4 quarter-note clicks per bar × 2 bars
}
