use crate::{
    GameState, abilities::Healing, loading_assets::GameAssets, utils::billboards::Billboard,
};
use bevy::prelude::*;
use rand::{Rng, thread_rng};
use shared::{loading_assets::AudioAssets, utils::observers::global_observer};

// Should not be a relationship because lightyear will silently track state for all relationships
// and break if one end of the relationship isn't replicated and is despawned
#[derive(Component)]
struct HasHealingEffects {
    effects: Entity,
}

#[derive(Component)]
struct HealingEffectsOf {
    of: Entity,
}

#[derive(Component, Default)]
#[require(Transform, InheritedVisibility)]
struct HealParticleEffect {
    next_spawn: f32,
}

#[derive(Component)]
struct HealParticle {
    start_scale: f32,
    end_scale: f32,
    start_pos: Vec3,
    end_pos: Vec3,
    start_time: f32,
    life_time: f32,
}

pub fn plugin(app: &mut App) {
    app.add_systems(
        Update,
        (on_added, update_effects, update_particles).run_if(in_state(GameState::Playing)),
    );

    global_observer!(app, on_removed);
}

fn on_added(
    mut commands: Commands,
    query: Query<Entity, Added<Healing>>,
    assets: Res<AudioAssets>,
) {
    for entity in query.iter() {
        let effects = commands
            .spawn((
                Name::new("heal-particle-effect"),
                HealParticleEffect::default(),
                AudioPlayer::new(assets.healing.clone()),
                PlaybackSettings {
                    mode: bevy::audio::PlaybackMode::Loop,
                    ..Default::default()
                },
                HealingEffectsOf { of: entity },
            ))
            .id();
        commands
            .entity(entity)
            .insert(HasHealingEffects { effects });
    }
}

fn on_removed(
    trigger: Trigger<OnRemove, Healing>,
    mut commands: Commands,
    effects: Query<&HasHealingEffects>,
) {
    let Ok(has_effects) = effects.get(trigger.target()) else {
        return;
    };
    commands.entity(has_effects.effects).try_despawn();
    commands
        .entity(trigger.target())
        .remove::<HasHealingEffects>();
}

fn update_effects(
    mut commands: Commands,
    mut query: Query<(&mut HealParticleEffect, &HealingEffectsOf, Entity)>,
    mut transforms: Query<&mut Transform>,
    time: Res<Time>,
    assets: Res<GameAssets>,
) {
    const DISTANCE: f32 = 4.;
    let mut rng = thread_rng();

    let now = time.elapsed_secs();
    for (mut effect, effects_of, e) in query.iter_mut() {
        // We have to manually track the healer's position because lightyear will try to synchronize
        // children and there's no reason to synchronize the particle effect entity when we're already
        // synchronizing `Healing`
        // (trying to ignore/avoid it by excluding the child from replication just causes crashes)
        let healer_pos = transforms.get(effects_of.of).unwrap().translation;
        transforms.get_mut(e).unwrap().translation = healer_pos;

        if effect.next_spawn < now {
            let start_pos = Vec3::new(
                rng.gen_range(-DISTANCE..DISTANCE),
                2.,
                rng.gen_range(-DISTANCE..DISTANCE),
            );
            let max_distance = start_pos.length().max(0.8);
            let end_pos =
                start_pos + (start_pos.normalize() * -1.) * rng.gen_range(0.5..max_distance);
            let start_scale = rng.gen_range(0.7..1.0);
            let end_scale = rng.gen_range(0.1..start_scale);

            commands.entity(e).with_child((
                Name::new("heal-particle"),
                SceneRoot(assets.mesh_heal_particle.clone()),
                Billboard::All,
                Transform::from_translation(start_pos),
                HealParticle {
                    start_scale,
                    end_scale,
                    start_pos,
                    end_pos,
                    start_time: now,
                    life_time: rng.gen_range(0.3..1.0),
                },
            ));

            effect.next_spawn = now + rng.gen_range(0.1..0.5);
        }
    }
}

fn update_particles(
    mut cmds: Commands,
    mut query: Query<(&mut Transform, &HealParticle, Entity)>,
    time: Res<Time>,
) {
    for (mut transform, particle, e) in query.iter_mut() {
        if particle.start_time + particle.life_time < time.elapsed_secs() {
            cmds.entity(e).despawn();
            continue;
        }

        let t = (time.elapsed_secs() - particle.start_time) / particle.life_time;

        // info!("particle[{e:?}] t: {t}");
        transform.translation = particle.start_pos.lerp(particle.end_pos, t);
        transform.scale = Vec3::splat(particle.start_scale.lerp(particle.end_scale, t));
    }
}
