use crate::prelude::{glam::Vec3Swizzles, *};
use rand::Rng;

use crate::utils::math::rot;

#[derive(Debug, Default)]
pub struct Tween {
    last_offset: Vec2,
    last_angle_offset: f32,
    shakes: Vec<Shake>,
    punches: Vec<RotationPunch>,
}

impl Tween {
    pub fn add_shake(&mut self, shake: Shake) {
        self.shakes.push(shake);
    }

    pub fn add_punch(&mut self, punch: RotationPunch) {
        self.punches.push(punch);
    }

    pub fn get_tween(&mut self, tick: f32, mut tf: Transform) -> Option<Transform> {
        let mut offset = Vec2::ZERO;
        let mut angle_offset = 0.0;

        self.shakes.retain_mut(|shake| {
            shake.update(tick);
            offset += shake.get_amplitude();
            shake.shaking()
        });

        self.punches.retain_mut(|punch| {
            punch.update(tick);
            angle_offset = rot(angle_offset, punch.get_angle_offset());
            punch.timer > 0.0
        });

        if offset == Vec2::ZERO
            && angle_offset == 0.0
            && self.last_offset == Vec2::ZERO
            && self.last_angle_offset == 0.0
        {
            return None;
        }
        tf.pos = (tf.pos.xy() + offset - self.last_offset).extend(tf.pos.z);
        let angle = tf.angle();
        let new_angle = rot(rot(angle, angle_offset), -self.last_angle_offset);
        tf.set_angle(new_angle);
        self.last_offset = offset;
        self.last_angle_offset = angle_offset;
        Some(tf)
    }

    // Update tweens
    pub fn update(
        g: &mut Engine,
        transforms: &mut Component<Transform>,
        tweens: &mut Component<Tween>,
    ) -> Result<()> {
        for e in tweens.iter_mut() {
            let id = e.id;
            let tween_state = &mut e.value;
            let tf = &mut transforms[id];
            if let Some(new_tf) = tween_state.get_tween(g.tick, *tf) {
                *tf = new_tf;
            }
        }

        Ok(())
    }
}

#[derive(Debug)]
pub struct RotationPunch {
    timer: f32,
    vibration: f32,
    samples: Vec<f32>,
}

impl RotationPunch {
    pub fn new(dir: Vec2, duration: f32, vibration: f32, elasticity: f32) -> Self {
        let sample_count = ((duration * vibration) as usize).max(2);
        let mut strength = dir.length();

        let decay_x_tween = strength / sample_count as f32;

        let mut total_duration = 0.0;
        let mut durations: Vec<f32> = (0..sample_count)
            .map(|i| {
                let perc = (i as f32 + 1.0) / sample_count as f32;
                let t = duration * perc;
                total_duration += t;
                t
            })
            .collect();

        let duration_multiplier = duration / total_duration;
        for t in &mut durations {
            *t *= duration_multiplier;
        }

        let samples: Vec<_> = durations
            .iter()
            .enumerate()
            .map(|(i, _t)| {
                if i < durations.len() - 1 {
                    let sample = if i == 0 {
                        dir.to_angle()
                    } else if i % 2 != 0 {
                        -dir.clamp_length_max(strength * elasticity).to_angle()
                    } else {
                        dir.clamp_length_max(strength).to_angle()
                    };
                    strength -= decay_x_tween;
                    sample
                } else {
                    0.0
                }
            })
            .collect();

        Self {
            timer: duration,
            vibration,
            samples,
        }
    }

    pub fn update(&mut self, tick: f32) {
        self.timer -= tick;
    }

    pub fn get_angle_offset(&self) -> f32 {
        if self.timer <= 0.0 {
            return 0.0;
        }
        let s = self.timer * self.vibration;
        let s0 = s.floor() as usize;
        let s1 = s0 + 1;
        let angle_s0 = self.samples.get(s0).copied().unwrap_or(0.0);
        let angle_s1 = self.samples.get(s1).copied().unwrap_or(0.0);

        rot(angle_s0, (s - s0 as f32) * (angle_s1 - angle_s0))
    }
}

#[derive(Debug)]
pub struct Shake {
    amplitude: f32,
    frequency: f32,
    samples: Vec<Vec2>,
    timer: f32,
    duration: f32,
}

impl Shake {
    pub fn new(amplitude: f32, duration: f32, frequency: f32) -> Self {
        let sample_count = (duration * frequency) as usize;
        let mut rng = rand::rng();
        let samples = (0..sample_count)
            .map(|_| Vec2::new(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0)))
            .collect();

        Shake {
            amplitude,
            frequency,
            samples,
            timer: duration,
            duration,
        }
    }

    fn shaking(&self) -> bool {
        self.timer > 0.0
    }

    fn update(&mut self, tick: f32) {
        self.timer -= tick;
    }

    fn get_amplitude(&self) -> Vec2 {
        if !self.shaking() {
            return Vec2::ZERO;
        }

        let s = self.timer * self.frequency;
        let s0 = s.floor() as usize;
        let s1 = s0 + 1;

        let noise_s0 = self.samples.get(s0).copied().unwrap_or(Vec2::ZERO);
        let noise_s1 = self.samples.get(s1).copied().unwrap_or(Vec2::ZERO);
        let k = self.decay_factor();
        let interp = noise_s0 + (s - s0 as f32) * (noise_s1 - noise_s0);

        self.amplitude * interp * k
    }

    fn decay_factor(&self) -> f32 {
        if self.timer < 0.0 {
            0.0
        } else {
            self.timer / self.duration
        }
    }
}
