//! Pure playback transport: index, play/pause, speed, frame stepping.

pub const SPEEDS: [f64; 5] = [0.25, 0.5, 1.0, 2.0, 4.0];

pub struct PlaybackState {
    index: usize,
    total: usize,
    playing: bool,
    speed_idx: usize,
    accum: f64,
}

impl PlaybackState {
    pub fn new(total: usize) -> Self {
        PlaybackState {
            index: 0,
            total: total.max(1),
            playing: false,
            speed_idx: 2, // 1.0x
            accum: 0.0,
        }
    }

    pub fn index(&self) -> usize {
        self.index
    }

    pub fn total(&self) -> usize {
        self.total
    }

    pub fn is_playing(&self) -> bool {
        self.playing
    }

    pub fn speed(&self) -> f64 {
        SPEEDS[self.speed_idx]
    }

    pub fn toggle_play(&mut self) {
        // Resuming from the end restarts from 0.
        if !self.playing && self.index >= self.total - 1 {
            self.index = 0;
        }
        self.playing = !self.playing;
        self.accum = 0.0;
    }

    pub fn faster(&mut self) {
        if self.speed_idx + 1 < SPEEDS.len() {
            self.speed_idx += 1;
        }
    }

    pub fn slower(&mut self) {
        if self.speed_idx > 0 {
            self.speed_idx -= 1;
        }
    }

    pub fn step(&mut self, delta: i64) {
        self.playing = false;
        self.accum = 0.0;
        let last = (self.total - 1) as i64;
        let next = (self.index as i64 + delta).clamp(0, last);
        self.index = next as usize;
    }

    pub fn seek(&mut self, index: usize) {
        self.index = index.min(self.total - 1);
    }

    pub fn to_start(&mut self) {
        self.index = 0;
    }

    pub fn to_end(&mut self) {
        self.index = self.total - 1;
    }

    pub fn advance(&mut self, dt_secs: f64) {
        if !self.playing {
            return;
        }
        self.accum += dt_secs * 60.0 * self.speed();
        let whole = self.accum.floor();
        if whole >= 1.0 {
            self.accum -= whole;
            let last = self.total - 1;
            self.index = (self.index + whole as usize).min(last);
            if self.index >= last {
                self.playing = false;
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn starts_paused_at_zero_at_1x() {
        let pb = PlaybackState::new(100);
        assert_eq!(pb.index(), 0);
        assert!(!pb.is_playing());
        assert_eq!(pb.speed(), 1.0);
    }

    #[test]
    fn advance_moves_60_frames_per_second_at_1x() {
        let mut pb = PlaybackState::new(1000);
        pb.toggle_play();
        pb.advance(1.0); // 1 second at 1x = 60 frames
        assert_eq!(pb.index(), 60);
    }

    #[test]
    fn advance_accumulates_fractional_frames() {
        let mut pb = PlaybackState::new(1000);
        pb.toggle_play();
        pb.advance(0.5); // 30 frames
        pb.advance(0.5); // +30 = 60
        assert_eq!(pb.index(), 60);
    }

    #[test]
    fn stops_at_end_and_does_not_overrun() {
        let mut pb = PlaybackState::new(10);
        pb.toggle_play();
        pb.advance(100.0);
        assert_eq!(pb.index(), 9);
        assert!(!pb.is_playing());
    }

    #[test]
    fn step_pauses_and_clamps() {
        let mut pb = PlaybackState::new(10);
        pb.toggle_play();
        pb.step(1);
        assert!(!pb.is_playing());
        assert_eq!(pb.index(), 1);
        pb.step(-5);
        assert_eq!(pb.index(), 0);
        pb.to_end();
        pb.step(5);
        assert_eq!(pb.index(), 9);
    }

    #[test]
    fn speed_clamps_within_table() {
        let mut pb = PlaybackState::new(10);
        pb.slower();
        pb.slower();
        pb.slower();
        assert_eq!(pb.speed(), SPEEDS[0]);
        for _ in 0..10 {
            pb.faster();
        }
        assert_eq!(pb.speed(), SPEEDS[SPEEDS.len() - 1]);
    }
}
