use rand::Rng;

use crate::components::particle::{ParticleEmitterConfig, SpawnParticleIntent};
use crate::render::particle_types::ParticleInstanceRaw;

pub struct Particle
{
    pub position: glam::Vec3,
    pub velocity: glam::Vec3,
    pub age: f32,
    pub lifetime: f32,
    pub size: f32,
    pub gravity: f32,
    pub color_start: [f32; 4],
    pub color_end: [f32; 4],
}

pub struct ParticleBuffers
{
    pub particles: Vec<Particle>,
    pub instances: Vec<ParticleInstanceRaw>,
    pub emit_accumulator: f32,
}

fn random_unit_vec(rng: &mut impl Rng) -> glam::Vec3
{
    loop
    {
        let v = glam::Vec3::new(
            rng.random_range(-1.0_f32..1.0),
            rng.random_range(-1.0_f32..1.0),
            rng.random_range(-1.0_f32..1.0),
        );
        let len_sq = v.length_squared();
        if len_sq <= 1.0 && len_sq > 0.0001
        {
            return v / len_sq.sqrt();
        }
    }
}

fn random_velocity(rng: &mut impl Rng, config: &ParticleEmitterConfig) -> glam::Vec3
{
    let speed = rng.random_range(config.speed.start..config.speed.end);
    let dir = if let Some(d) = config.direction
    {
        let perp = if d.x.abs() < 0.9
        {
            glam::Vec3::X
        }
        else
        {
            glam::Vec3::Y
        };
        let right = d.cross(perp).normalize();
        let up = d.cross(right);
        let angle = rng.random_range(0.0_f32..config.direction_spread);
        let azimuth = rng.random_range(0.0_f32..(2.0 * std::f32::consts::PI));
        (d * angle.cos() + right * angle.sin() * azimuth.cos() + up * angle.sin() * azimuth.sin())
            .normalize()
    }
    else
    {
        random_unit_vec(rng)
    };
    dir * speed
}

fn spawn_from_config(
    buffers: &mut ParticleBuffers,
    origin: glam::Vec3,
    config: &ParticleEmitterConfig,
    rng: &mut impl Rng,
)
{
    for _ in 0..config.burst_count
    {
        let lifetime = rng.random_range(config.lifetime.start..config.lifetime.end);
        let size = rng.random_range(config.size.start..config.size.end);
        let velocity = random_velocity(rng, config);
        buffers.particles.push(Particle {
            position: origin,
            velocity,
            age: 0.0,
            lifetime,
            size,
            gravity: config.gravity,
            color_start: config.color_start,
            color_end: config.color_end,
        });
    }
}

pub fn particle_intent_system(
    particle_buffers: &mut Option<ParticleBuffers>,
    spawn_particle_intents: &mut Vec<SpawnParticleIntent>,
)
{
    if particle_buffers.is_none()
    {
        *particle_buffers = Some(ParticleBuffers {
            particles: Vec::new(),
            instances: Vec::new(),
            emit_accumulator: 0.0,
        });
    }

    let intents: Vec<SpawnParticleIntent> = spawn_particle_intents.drain(..).collect();
    if intents.is_empty()
    {
        return;
    }

    let buffers = particle_buffers.as_mut().unwrap();
    let mut rng = rand::rng();
    for intent in intents
    {
        spawn_from_config(buffers, intent.origin, &intent.config, &mut rng);
    }
}

pub fn particle_update_system(particle_buffers: &mut Option<ParticleBuffers>, delta: f32)
{
    let Some(ref mut buffers) = particle_buffers
    else
    {
        return;
    };

    for particle in &mut buffers.particles
    {
        particle.velocity.y -= particle.gravity * delta;
        particle.position += particle.velocity * delta;
        particle.age += delta / particle.lifetime;
    }

    buffers.particles.retain(|p| p.age < 1.0);
}

pub fn spawn_snow_particles(buffers: &mut ParticleBuffers, camera_pos: glam::Vec3, delta: f32)
{
    let mut rng = rand::rng();

    let rate = 200.0_f32;
    buffers.emit_accumulator += delta;
    let to_emit = (buffers.emit_accumulator * rate) as u32;
    buffers.emit_accumulator -= to_emit as f32 / rate;

    for _ in 0..to_emit
    {
        let x = camera_pos.x + rng.random_range(-20.0_f32..20.0_f32);
        let z = camera_pos.z + rng.random_range(-20.0_f32..20.0_f32);
        let y = rng.random_range((camera_pos.y + 8.0)..(camera_pos.y + 20.0));
        let vx = rng.random_range(-0.3_f32..0.3_f32);
        let vy = rng.random_range(-2.0_f32..-0.5_f32);
        let vz = rng.random_range(-0.3_f32..0.3_f32);
        let size = rng.random_range(0.05_f32..0.15_f32);
        let lifetime = rng.random_range(2.0_f32..5.0_f32);
        buffers.particles.push(Particle {
            position: glam::Vec3::new(x, y, z),
            velocity: glam::Vec3::new(vx, vy, vz),
            age: 0.0,
            lifetime,
            size,
            gravity: 0.0,
            color_start: [1.0, 1.0, 1.0, 1.0],
            color_end: [1.0, 1.0, 1.0, 0.0],
        });
    }

    buffers
        .particles
        .retain(|p| p.position.y >= camera_pos.y - 5.0);
}

pub fn collect_instances(buffers: &mut ParticleBuffers) -> &[ParticleInstanceRaw]
{
    buffers.instances.clear();
    for particle in &buffers.particles
    {
        let t = particle.age;
        let color = [
            particle.color_start[0] + (particle.color_end[0] - particle.color_start[0]) * t,
            particle.color_start[1] + (particle.color_end[1] - particle.color_start[1]) * t,
            particle.color_start[2] + (particle.color_end[2] - particle.color_start[2]) * t,
            particle.color_start[3] + (particle.color_end[3] - particle.color_start[3]) * t,
        ];
        buffers.instances.push(ParticleInstanceRaw {
            position: particle.position.into(),
            velocity: particle.velocity.into(),
            size: particle.size,
            color,
            age: t,
            _padding: [0.0; 3],
        });
    }
    &buffers.instances
}
