//! Look-ahead scheduler timing — Stage 2 of the Rust port (see `docs/rust-port.md`).
//!
//! Deterministic: turns a parsed `Track` + bpm into the click timeline. This is the same
//! `durs` math the firmware uses (`app.py` `_prepare_next` / `tick`) and the engine.js
//! scheduler primitives marked "PORTS TO FIRMWARE" — the real-time firmware loop just
//! plays this timeline against the wall clock. Host-testable because the *timing grid*
//! is pure.

use crate::{Lane, Track};
use alloc::vec::Vec;

const NS_PER_MIN: i64 = 60_000_000_000;

#[derive(Debug, Clone, PartialEq)]
pub struct Click {
    pub time_ns: i64,
    pub lane: usize,
    pub level: u8,
}

/// The master bar length in ns — defined by the first lane (beats = steps / sub).
pub fn master_bar_ns(track: &Track) -> i64 {
    let beat = NS_PER_MIN / track.bpm.max(1);
    let m = &track.lanes[0];
    let beats = (m.levels.len().max(1) / m.sub.max(1) as usize) as i64;
    beat * beats.max(1)
}

/// Per-step durations (ns) for a lane. Mirrors the firmware exactly:
/// - poly  → the lane's steps are spread evenly across the master bar (true polymeter)
/// - swing → even subdivisions split 2/3 (on-beat) + 1/3 (off-beat)
/// - else  → uniform beat/sub
pub fn lane_durs(lane: &Lane, bpm: i64, master_bar_ns: i64) -> Vec<i64> {
    let beat = NS_PER_MIN / bpm.max(1);
    let sub = lane.sub.max(1) as i64;
    let steps = lane.levels.len().max(1) as i64;
    if lane.poly {
        let d = master_bar_ns / steps;
        vec![d; steps as usize]
    } else if lane.swing && sub % 2 == 0 {
        let pair = beat / (sub / 2).max(1);
        let lng = pair * 2 / 3;
        let sht = pair / 3;
        (0..steps)
            .map(|s| if (s % sub) % 2 == 0 { lng } else { sht })
            .collect()
    } else {
        let d = beat / sub;
        vec![d; steps as usize]
    }
}

/// Render the deterministic click timeline over `bars` master bars.
/// Every lane starts together at t=0 and loops; muted lanes and rests (level 0) are dropped.
pub fn render(track: &Track, bars: i64) -> Vec<Click> {
    let mbar = master_bar_ns(track);
    let total = mbar * bars.max(1);
    let mut out = Vec::new();
    for (li, lane) in track.lanes.iter().enumerate() {
        if lane.mute {
            continue;
        }
        let durs = lane_durs(lane, track.bpm, mbar);
        if durs.iter().all(|&d| d <= 0) {
            continue;
        }
        let mut t = 0i64;
        let mut s = 0usize;
        while t < total {
            let lvl = lane.levels[s];
            if lvl > 0 {
                out.push(Click { time_ns: t, lane: li, level: lvl });
            }
            t += durs[s].max(1);
            s = (s + 1) % durs.len();
        }
    }
    out.sort_by_key(|c| (c.time_ns, c.lane));
    out
}
