use super::TriggerThrow;
use crate::{
    GameState,
    abilities::BuildExplosionSprite,
    heads_database::HeadsDatabase,
    physics_layers::GameLayer,
    protocol::{GltfSceneRoot, PlaySound},
    utils::{
        auto_rotate::AutoRotation, commands::CommandExt, explosions::Explosion, global_observer,
    },
};
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy_ballistic::launch_velocity;
#[cfg(feature = "server")]
use lightyear::prelude::{NetworkTarget, Replicate};
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;

#[derive(Component, Serialize, Deserialize, PartialEq)]
pub struct ThrownProjectile {
    impact_animation: bool,
    damage: u32,
}

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

    global_observer!(app, on_trigger_thrown);
}

fn on_trigger_thrown(
    trigger: Trigger<TriggerThrow>,
    mut commands: Commands,
    query_transform: Query<&Transform>,
    heads_db: Res<HeadsDatabase>,
) {
    let state = trigger.event().0;

    commands.trigger(PlaySound::Throw);

    const SPEED: f32 = 35.;

    let pos = state.pos;

    let vel = if let Some(target) = state.target
        && let Ok(t) = query_transform.get(target)
    {
        launch_velocity(pos, t.translation, SPEED, 9.81)
            .map(|(low, _)| low)
            .unwrap()
    } else {
        state.rot.mul_quat(Quat::from_rotation_y(-PI / 2.))
            * (Vec3::new(2., 1., 0.).normalize() * SPEED)
    };

    let head = heads_db.head_stats(state.head);

    //TODO: projectile db?
    let explosion_animation = !matches!(state.head, 8 | 16);

    let mut projectile = commands.spawn((
        Transform::from_translation(pos),
        Name::new("projectile-thrown"),
        ThrownProjectile {
            impact_animation: explosion_animation,
            damage: head.damage,
        },
        Collider::sphere(0.4),
        CollisionLayers::new(
            LayerMask(GameLayer::Projectile.to_bits()),
            LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
        ),
        RigidBody::Dynamic,
        CollisionEventsEnabled,
        Mass(0.01),
        LinearVelocity(vel),
        Visibility::default(),
        Sensor,
    ));
    projectile.with_child((
        AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
        GltfSceneRoot::Projectile(head.projectile.clone()),
    ));

    #[cfg(feature = "server")]
    projectile.insert(Replicate::to_clients(NetworkTarget::All));
}

fn shot_collision(
    mut commands: Commands,
    mut collision_event_reader: EventReader<CollisionStarted>,
    query_shot: Query<(&ThrownProjectile, &Transform)>,
    sensors: Query<(), With<Sensor>>,
) {
    for CollisionStarted(e1, e2) in collision_event_reader.read() {
        if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
            continue;
        }

        if sensors.contains(*e1) && sensors.contains(*e2) {
            continue;
        }

        let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };

        let Ok((shot_pos, animation, damage)) =
            query_shot.get(shot_entity).map(|(projectile, t)| {
                (
                    t.translation,
                    projectile.impact_animation,
                    projectile.damage,
                )
            })
        else {
            continue;
        };

        if let Ok(mut entity) = commands.get_entity(shot_entity) {
            entity.try_despawn();
        } else {
            continue;
        }

        commands.trigger(PlaySound::ThrowHit);

        commands.trigger(Explosion {
            damage,
            position: shot_pos,
            //TODO: should be around 1 grid in distance
            radius: 5.,
        });

        //TODO: support different impact animations
        if animation {
            commands.trigger_server(BuildExplosionSprite {
                pos: shot_pos,
                pixels_per_meter: 32.,
                time: 0.02,
            });
        }
    }
}
