use crate::{
    GameState, control::controller_common::MovementSpeedFactor, global_observer, player::Player,
    tb_entities::Water,
};
use avian3d::prelude::*;
use bevy::prelude::*;

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

#[derive(Event)]
struct PlayerInWater {
    player: Entity,
    entered: bool,
}

pub fn plugin(app: &mut App) {
    app.add_systems(OnEnter(GameState::Playing), setup);
    app.add_systems(
        Update,
        check_water_collision.run_if(in_state(GameState::Playing)),
    );
    global_observer!(app, on_player_water);
}

fn setup(mut commands: Commands, query: Query<(Entity, &Children), With<Water>>) {
    for (e, c) in query.iter() {
        assert!(c.len() == 1);
        let child = c.iter().next().unwrap();

        commands.entity(e).insert((
            Sensor,
            CollisionEventsEnabled,
            ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh),
        ));

        commands.entity(child).insert(WaterSensor);
    }
}

fn check_water_collision(
    mut cmds: Commands,
    mut collisionstart_events: EventReader<CollisionStarted>,
    mut collisionend_events: EventReader<CollisionEnded>,
    query_player: Query<&Player>,
    query_water: Query<(Entity, &WaterSensor)>,
) {
    let start_events = collisionstart_events
        .read()
        .map(|CollisionStarted(e1, e2)| (true, *e1, *e2));
    let end_events = collisionend_events
        .read()
        .map(|CollisionEnded(e1, e2)| (false, *e1, *e2));

    for (started, e1, e2) in start_events.chain(end_events) {
        let entities = [e1, e2];

        let player = entities
            .iter()
            .find(|e| query_player.contains(**e))
            .copied();
        let water = entities.iter().find(|e| query_water.contains(**e)).copied();

        if !(player.is_some() && water.is_some()) {
            continue;
        }

        let Some(player) = player else {
            continue;
        };

        cmds.trigger(PlayerInWater {
            player,
            entered: started,
        });
    }
}

fn on_player_water(
    trigger: Trigger<PlayerInWater>,
    //TODO: use a sparse set component `InWater` that we attach to the player
    // then we can have a movement factor system that reacts on these components to update the factor
    // PLUS we can then always adhoc check if a player is `InWater` to play an according sound and such
    mut query: Query<&mut MovementSpeedFactor, With<Player>>,
) {
    let player = trigger.player;

    let Ok(mut factor) = query.get_mut(player) else {
        return;
    };

    factor.0 = if trigger.entered { 0.5 } else { 1. };
}
