use crate::{
    billboards::Billboard,
    global_observer,
    physics_layers::GameLayer,
    player::Player,
    protocol::{GltfSceneRoot, PlaySound},
    squish_animation::SquishAnimation,
};
use avian3d::prelude::*;
use bevy::{
    ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
    prelude::*,
};
#[cfg(feature = "server")]
use lightyear::prelude::{NetworkTarget, Replicate};
use std::f32::consts::PI;

#[derive(Event, Reflect)]
pub struct KeySpawn(pub Vec3, pub String);

#[derive(Component, Reflect)]
#[reflect(Component)]
struct Key(pub String);

#[derive(Event, Reflect)]
pub struct KeyCollected(pub String);

pub fn plugin(app: &mut App) {
    global_observer!(app, on_spawn_key);
}

fn on_spawn_key(trigger: Trigger<KeySpawn>, mut commands: Commands) {
    let KeySpawn(position, id) = trigger.event();

    let id = id.clone();

    let angle = rand::random::<f32>() * PI * 2.;
    let spawn_dir = Quat::from_rotation_y(angle) * Vec3::new(0.5, 0.6, 0.).normalize();

    commands.spawn((
        Name::new("key"),
        Transform::from_translation(*position),
        Visibility::default(),
        Collider::sphere(1.5),
        ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
        LockedAxes::ROTATION_LOCKED,
        RigidBody::Dynamic,
        CollisionLayers::new(GameLayer::CollectiblePhysics, GameLayer::Level),
        Restitution::new(0.6),
        Children::spawn((
            Spawn((Billboard::All, SquishAnimation(2.6), GltfSceneRoot::Key)),
            SpawnWith(|parent: &mut RelatedSpawner<ChildOf>| {
                parent
                    .spawn((
                        Collider::sphere(1.5),
                        CollisionLayers::new(GameLayer::CollectibleSensors, GameLayer::Player),
                        Sensor,
                        CollisionEventsEnabled,
                        Key(id),
                    ))
                    .observe(on_collect_key);
            }),
        )),
        #[cfg(feature = "server")]
        Replicate::to_clients(NetworkTarget::All),
    ));
}

fn on_collect_key(
    trigger: Trigger<OnCollisionStart>,
    mut commands: Commands,
    query_player: Query<&Player>,
    query_collectable: Query<(&Key, &ChildOf)>,
) {
    let key = trigger.target();
    let collider = trigger.collider;

    if query_player.contains(collider) {
        let (key, child_of) = query_collectable.get(key).unwrap();

        commands.trigger(PlaySound::KeyCollect);
        commands.trigger(KeyCollected(key.0.clone()));
        commands.entity(child_of.parent()).despawn();
    }
}
