use avian3d::{math::*, prelude::*};
use bevy::prelude::*;

use crate::{
    GameState, control::collisions::kinematic_controller_collisions, player::PlayerBodyMesh,
};

use super::ControllerSwitchEvent;

pub fn plugin(app: &mut App) {
    app.init_resource::<PlayerMovement>();
    app.init_resource::<MovementSettings>();

    app.register_type::<MovementSettings>();
    app.register_type::<MovementDampingFactor>();
    app.register_type::<JumpImpulse>();
    app.register_type::<ControllerGravity>();
    app.register_type::<MovementSpeed>();

    app.add_systems(
        // Run collision handling after collision detection.
        //
        // NOTE: The collision implementation here is very basic and a bit buggy.
        //       A collide-and-slide algorithm would likely work better.
        PhysicsSchedule,
        kinematic_controller_collisions
            .in_set(NarrowPhaseSet::Last)
            .run_if(in_state(GameState::Playing)),
    );
}

/// Reset the pitch and velocity of the character if the controller was switched.
pub fn reset_upon_switch(
    mut event_controller_switch: EventReader<ControllerSwitchEvent>,
    mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
    mut controllers: Query<&mut LinearVelocity>,
) {
    for _ in event_controller_switch.read() {
        // Reset velocity
        for mut linear_velocity in &mut controllers {
            linear_velocity.0 = Vec3::ZERO;
        }

        // Reset pitch but keep yaw the same
        if let Some(ref mut rig_transform) = rig_transform_q {
            let euler_rot = rig_transform.rotation.to_euler(EulerRot::YXZ);
            let yaw = euler_rot.0;
            rig_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, 0.0, 0.0);
        }
    }
}

#[derive(Resource, Default)]
pub struct PlayerMovement {
    pub any_direction: bool,
    pub shooting: bool,
}

/// A marker component indicating that an entity is using a character controller.
#[derive(Component)]
pub struct CharacterController;

/// A marker component indicating that an entity is on the ground.
#[derive(Component)]
#[component(storage = "SparseSet")]
pub struct Grounded;

#[derive(Resource, Reflect)]
#[reflect(Resource)]
pub struct MovementSettings {
    pub damping_normal: f32,
    pub damping_brake: f32,
    pub damping_brake_air: f32,
}

// todo some duplicate with player.rs settings
impl Default for MovementSettings {
    fn default() -> Self {
        Self {
            damping_normal: 1.0,
            damping_brake: 30.0,
            damping_brake_air: 1.0,
        }
    }
}

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

/// The speed used for character movement.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct MovementSpeed(pub Scalar);

/// The damping factor used for slowing down movement.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct MovementDampingFactor(pub Scalar);

/// The strength of a jump.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct JumpImpulse(pub Scalar);

/// The gravitational acceleration used for a character controller.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct ControllerGravity(pub Vector);

/// The maximum angle a slope can have for a character controller
/// to be able to climb and jump. If the slope is steeper than this angle,
/// the character will slide down.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct MaxSlopeAngle(pub Scalar);

/// A bundle that contains the components needed for a basic
/// kinematic character controller.
#[derive(Bundle)]
pub struct CharacterControllerBundle {
    character_controller: CharacterController,
    rigid_body: RigidBody,
    collider: Collider,
    ground_caster: ShapeCaster,
    gravity: ControllerGravity,
    movement: MovementBundle,
    collision_events: CollisionEventsEnabled,
}

/// A bundle that contains components for character movement.
#[derive(Bundle)]
pub struct MovementBundle {
    acceleration: MovementSpeed,
    jump_impulse: JumpImpulse,
    max_slope_angle: MaxSlopeAngle,
    factor: MovementSpeedFactor,
}

impl MovementBundle {
    pub const fn new(acceleration: Scalar, jump_impulse: Scalar, max_slope_angle: Scalar) -> Self {
        Self {
            acceleration: MovementSpeed(acceleration),
            jump_impulse: JumpImpulse(jump_impulse),
            max_slope_angle: MaxSlopeAngle(max_slope_angle),
            factor: MovementSpeedFactor(1.0),
        }
    }
}

impl Default for MovementBundle {
    fn default() -> Self {
        Self::new(30.0, 18.0, (60.0 as Scalar).to_radians())
    }
}

impl CharacterControllerBundle {
    pub fn new(collider: Collider, gravity: Vector) -> Self {
        // Create shape caster as a slightly smaller version of collider
        let mut caster_shape = collider.clone();
        caster_shape.set_scale(Vector::ONE * 0.98, 10);

        Self {
            character_controller: CharacterController,
            rigid_body: RigidBody::Kinematic,
            collider,
            ground_caster: ShapeCaster::new(
                caster_shape,
                Vector::ZERO,
                Quaternion::default(),
                Dir3::NEG_Y,
            )
            .with_max_distance(0.2),
            gravity: ControllerGravity(gravity),
            movement: MovementBundle::default(),
            collision_events: CollisionEventsEnabled,
        }
    }
}
