use core::num::NonZeroU32;
use std::time::Duration;

use bone_ui::input::FrameInstant;
use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "u32", into = "u32")]
pub struct FrameCount(NonZeroU32);

#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("frame count must be a positive integer, got {0}")]
pub struct ZeroFrameCount(u32);

impl FrameCount {
    pub const ONE: Self = Self(NonZeroU32::MIN);

    #[must_use]
    pub const fn new(frames: NonZeroU32) -> Self {
        Self(frames)
    }

    #[must_use]
    pub const fn get(self) -> u32 {
        self.0.get()
    }
}

impl TryFrom<u32> for FrameCount {
    type Error = ZeroFrameCount;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        NonZeroU32::new(value)
            .map(Self)
            .ok_or(ZeroFrameCount(value))
    }
}

impl From<FrameCount> for u32 {
    fn from(value: FrameCount) -> Self {
        value.0.get()
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FrameClock(FrameInstant);

impl FrameClock {
    pub const FIXED_STEP: Duration = Duration::from_micros(16_667);

    #[must_use]
    pub const fn start() -> Self {
        Self(FrameInstant::ZERO)
    }

    #[must_use]
    pub const fn now(self) -> FrameInstant {
        self.0
    }

    pub fn feed(&mut self, now: FrameInstant) {
        self.0 = self.0.max(now);
    }

    pub fn advance(&mut self, frames: FrameCount) {
        self.0 = self.0.after(Self::FIXED_STEP.saturating_mul(frames.get()));
    }
}

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

    fn frames(n: u32) -> FrameCount {
        match NonZeroU32::new(n) {
            Some(count) => FrameCount::new(count),
            None => panic!("frame count must be nonzero"),
        }
    }

    #[test]
    fn advance_steps_exactly_one_fixed_step_per_frame() {
        let mut clock = FrameClock::start();
        clock.advance(frames(60));
        assert_eq!(
            clock.now().duration(),
            FrameClock::FIXED_STEP * 60,
            "sixty fixed steps land on a bit-exact instant"
        );
    }

    #[test]
    fn feed_never_rewinds() {
        let mut clock = FrameClock::start();
        clock.feed(FrameInstant::from_duration(Duration::from_millis(100)));
        clock.feed(FrameInstant::from_duration(Duration::from_millis(40)));
        assert_eq!(
            clock.now(),
            FrameInstant::from_duration(Duration::from_millis(100)),
            "a stale feed must not move time backward"
        );
    }

    #[test]
    fn a_180ms_animation_finishes_on_frame_11() {
        let finished_at = (1_u32..=20).find(|frame| {
            let mut clock = FrameClock::start();
            clock.advance(frames(*frame));
            clock.now().duration() >= Duration::from_millis(180)
        });
        assert_eq!(
            finished_at,
            Some(11),
            "a 180 ms tween completes on a known frame under fixed step"
        );
    }
}
