use std::f32::consts::PI;

use avian3d::prelude::*;
use bevy::prelude::*;

use crate::player::PlayerBodyMesh;

use super::{
    ControlState, ControllerSet,
    controller_common::{MovementDampingFactor, MovementSpeed},
};

pub struct CharacterControllerPlugin;

impl Plugin for CharacterControllerPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            Update,
            (rotate_rig, movement, apply_movement_damping)
                .chain()
                .in_set(ControllerSet::ApplyControlsFly), // todo only in GameState::Playing?
        );
    }
}

fn rotate_rig(
    mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
    controls: Res<ControlState>,
) {
    if controls.view_mode {
        return;
    }

    let look_dir = controls.look_dir;

    if let Some(ref mut rig_transform) = rig_transform_q {
        // todo: Make consistent with the running controller
        let sensitivity = 0.001;
        let max_pitch = 35.0 * PI / 180.0;
        let min_pitch = -25.0 * PI / 180.0;

        rig_transform.rotate_y(look_dir.x * -sensitivity);

        let euler_rot = rig_transform.rotation.to_euler(EulerRot::YXZ);
        let yaw = euler_rot.0;
        let pitch = euler_rot.1 + look_dir.y * sensitivity;

        let pitch_clamped = pitch.clamp(min_pitch, max_pitch);
        rig_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch_clamped, 0.0);

        // The following can be used to limit the amount of rotation per frame
        // let target_rotation = rig_transform.rotation
        //     * Quat::from_rotation_x(controls.keyboard_state.look_dir.y * sensitivity);
        // let clamped_rotation = rig_transform.rotation.rotate_towards(target_rotation, 0.01);
        // rig_transform.rotation = clamped_rotation;
    }
}

/// Responds to [`MovementAction`] events and moves character controllers accordingly.
fn movement(
    mut controllers: Query<(&MovementSpeed, &mut LinearVelocity)>,
    rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
    time: Res<Time>,
) {
    let move_dir = Vec2::new(0.0, 70.) * time.delta_secs();

    for (movement_acceleration, mut linear_velocity) in &mut controllers {
        let mut direction = move_dir.extend(0.0).xzy();

        if let Some(ref rig_transform) = rig_transform_q {
            direction =
                (rig_transform.forward() * direction.z) + (rig_transform.right() * direction.x);
        }

        linear_velocity.0 = -direction * movement_acceleration.0;
    }
}

/// Slows down movement in the XZ plane.
fn apply_movement_damping(
    time: Res<Time>,
    mut query: Query<(&MovementDampingFactor, &mut LinearVelocity)>,
) {
    let delta_time = time.delta_secs();

    for (damping_factor, mut linear_velocity) in &mut query {
        // We could use `LinearDamping`, but we don't want to dampen movement along the Y axis
        linear_velocity.x *= 1.0 - damping_factor.0 * delta_time;
        linear_velocity.z *= 1.0 - damping_factor.0 * delta_time;
    }
}
