use crate::{
    GameState,
    billboards::Billboard,
    global_observer,
    heads_database::HeadsDatabase,
    physics_layers::GameLayer,
    player::Player,
    protocol::{GltfSceneRoot, PlaySound},
    squish_animation::SquishAnimation,
    tb_entities::SecretHead,
};
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 HeadDrops {
    pos: Vec3,
    head_id: usize,
    impulse: bool,
}

impl HeadDrops {
    pub fn new(pos: Vec3, head_id: usize) -> Self {
        Self {
            pos,
            head_id,
            impulse: true,
        }
    }

    fn new_static(pos: Vec3, head_id: usize) -> Self {
        Self {
            pos,
            head_id,
            impulse: false,
        }
    }
}

#[derive(Component, Reflect)]
#[reflect(Component)]
struct HeadDrop {
    pub head_id: usize,
}

#[derive(Component, Reflect)]
#[reflect(Component)]
struct HeadDropEnableTime(f32);

#[derive(Component, Reflect)]
#[reflect(Component)]
struct SecretHeadMarker;

#[derive(Event, Reflect)]
pub struct HeadCollected(pub usize);

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

    app.add_systems(OnEnter(GameState::Playing), spawn);

    global_observer!(app, on_head_drop);
}

fn spawn(mut commands: Commands, query: Query<(Entity, &GlobalTransform, &SecretHead)>) {
    for (e, t, head) in query {
        commands.trigger(HeadDrops::new_static(
            t.translation() + Vec3::new(0., 2., 0.),
            head.head_id,
        ));

        commands.entity(e).despawn();
    }
}

fn on_head_drop(
    trigger: Trigger<HeadDrops>,
    mut commands: Commands,
    heads_db: Res<HeadsDatabase>,
    time: Res<Time>,
) -> Result<(), BevyError> {
    let drop = trigger.event();

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

    if drop.impulse {
        commands.trigger(PlaySound::HeadDrop);
    }

    let mesh_addr = format!("{:?}", heads_db.head_stats(drop.head_id).ability).to_lowercase();

    commands
        .spawn((
            Name::new("headdrop"),
            Transform::from_translation(drop.pos),
            Visibility::default(),
            Collider::sphere(1.5),
            LockedAxes::ROTATION_LOCKED,
            RigidBody::Dynamic,
            CollisionLayers::new(
                GameLayer::CollectiblePhysics,
                LayerMask::ALL & !GameLayer::Player.to_bits(),
            ),
            Restitution::new(0.6),
            Children::spawn(SpawnWith({
                let head_id = drop.head_id;
                let now = time.elapsed_secs();
                move |parent: &mut RelatedSpawner<ChildOf>| {
                    parent
                        .spawn((
                            Collider::sphere(1.5),
                            CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::NONE),
                            Sensor,
                            CollisionEventsEnabled,
                            HeadDrop { head_id },
                            HeadDropEnableTime(now + 1.2),
                        ))
                        .observe(on_collect_head);
                }
            })),
            #[cfg(feature = "server")]
            Replicate::to_clients(NetworkTarget::All),
        ))
        .insert_if(
            ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
            || drop.impulse,
        )
        .with_child((
            Billboard::All,
            SquishAnimation(2.6),
            GltfSceneRoot::HeadDrop(mesh_addr),
        ));

    Ok(())
}

fn enable_collectible(
    mut commands: Commands,
    query: Query<(Entity, &HeadDropEnableTime)>,
    time: Res<Time>,
) {
    let now = time.elapsed_secs();
    for (e, enable_time) in query.iter() {
        if now > enable_time.0 {
            commands
                .entity(e)
                .insert(CollisionLayers::new(
                    LayerMask(GameLayer::CollectibleSensors.to_bits()),
                    LayerMask::ALL,
                ))
                .remove::<HeadDropEnableTime>();
        }
    }
}

fn on_collect_head(
    trigger: Trigger<OnCollisionStart>,

    mut commands: Commands,
    query_player: Query<&Player>,
    query_collectable: Query<(&HeadDrop, &ChildOf)>,
    query_secret: Query<&SecretHeadMarker>,
) {
    let collectable = trigger.target();
    let collider = trigger.collider;

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

        let is_secret = query_secret.contains(collectable);

        if is_secret {
            commands.trigger(PlaySound::SecretHeadCollect);
        } else {
            commands.trigger(PlaySound::HeadCollect);
        }

        commands
            .entity(collider)
            .trigger(HeadCollected(drop.head_id));
        commands.entity(child_of.parent()).despawn();
    }
}
