use super::easing::EasingFunction;
use nalgebra_glm::{Vec2, Vec3, Vec4};

#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum TweenValue {
    F32(f32),
    Vec2(Vec2),
    Vec3(Vec3),
    Vec4(Vec4),
}

impl TweenValue {
    pub fn lerp(from: &Self, to: &Self, t: f32) -> Self {
        match (from, to) {
            (Self::F32(a), Self::F32(b)) => Self::F32(a + (b - a) * t),
            (Self::Vec2(a), Self::Vec2(b)) => Self::Vec2(nalgebra_glm::lerp(a, b, t)),
            (Self::Vec3(a), Self::Vec3(b)) => Self::Vec3(nalgebra_glm::lerp(a, b, t)),
            (Self::Vec4(a), Self::Vec4(b)) => Self::Vec4(nalgebra_glm::lerp(a, b, t)),
            _ => *from,
        }
    }
}

#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
pub enum TweenState {
    #[default]
    Playing,
    Paused,
    Completed,
}

#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
pub enum TweenLoopMode {
    #[default]
    Once,
    Loop,
    PingPong,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TweenTrack {
    pub tag: u32,
    pub from: TweenValue,
    pub to: TweenValue,
    pub duration: f32,
    pub elapsed: f32,
    pub easing: EasingFunction,
    pub loop_mode: TweenLoopMode,
    pub state: TweenState,
    pub current: TweenValue,
    pub speed: f32,
    pub ping_pong_forward: bool,
    pub delay: f32,
    pub delay_remaining: f32,
}

impl TweenTrack {
    pub fn new(from: TweenValue, to: TweenValue, duration: f32) -> Self {
        Self {
            tag: 0,
            from,
            to,
            duration,
            elapsed: 0.0,
            easing: EasingFunction::default(),
            loop_mode: TweenLoopMode::default(),
            state: TweenState::Playing,
            current: from,
            speed: 1.0,
            ping_pong_forward: true,
            delay: 0.0,
            delay_remaining: 0.0,
        }
    }

    pub fn with_easing(mut self, easing: EasingFunction) -> Self {
        self.easing = easing;
        self
    }

    pub fn with_loop_mode(mut self, loop_mode: TweenLoopMode) -> Self {
        self.loop_mode = loop_mode;
        self
    }

    pub fn with_tag(mut self, tag: u32) -> Self {
        self.tag = tag;
        self
    }

    pub fn with_delay(mut self, delay: f32) -> Self {
        self.delay = delay;
        self.delay_remaining = delay;
        self
    }

    pub fn with_speed(mut self, speed: f32) -> Self {
        self.speed = speed;
        self
    }

    pub fn value_f32(&self) -> f32 {
        match self.current {
            TweenValue::F32(value) => value,
            _ => 0.0,
        }
    }

    pub fn value_vec2(&self) -> Vec2 {
        match self.current {
            TweenValue::Vec2(value) => value,
            _ => Vec2::zeros(),
        }
    }

    pub fn value_vec3(&self) -> Vec3 {
        match self.current {
            TweenValue::Vec3(value) => value,
            _ => Vec3::zeros(),
        }
    }

    pub fn value_vec4(&self) -> Vec4 {
        match self.current {
            TweenValue::Vec4(value) => value,
            _ => Vec4::zeros(),
        }
    }

    pub fn progress(&self) -> f32 {
        if self.duration <= 0.0 {
            1.0
        } else {
            (self.elapsed / self.duration).clamp(0.0, 1.0)
        }
    }

    pub fn is_completed(&self) -> bool {
        self.state == TweenState::Completed
    }
}

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Tween {
    pub tracks: Vec<TweenTrack>,
}

impl Tween {
    pub fn new() -> Self {
        Self { tracks: Vec::new() }
    }

    pub fn add_track(&mut self, track: TweenTrack) {
        self.tracks.push(track);
    }

    pub fn track_by_tag(&self, tag: u32) -> Option<&TweenTrack> {
        self.tracks.iter().find(|track| track.tag == tag)
    }

    pub fn track_by_tag_mut(&mut self, tag: u32) -> Option<&mut TweenTrack> {
        self.tracks.iter_mut().find(|track| track.tag == tag)
    }

    pub fn all_completed(&self) -> bool {
        self.tracks.iter().all(|track| track.is_completed())
    }

    pub fn remove_completed(&mut self) {
        self.tracks.retain(|track| !track.is_completed());
    }
}
