use bevy::{
    input::mouse::AccumulatedMouseMotion, pbr::NotShadowCaster, prelude::*,
    render::view::RenderLayers, window::PrimaryWindow,
};
use bevy_rapier3d::prelude::*;

use crate::GameState;

mod toolbar;

pub fn plugin(app: &mut App) {
    app.add_plugins(toolbar::plugin)
        .add_systems(OnEnter(GameState::Playing), (init_player, hide_cursor))
        .add_systems(
            Update,
            (move_camera, handle_input).run_if(in_state(GameState::Playing)),
        )
        .add_systems(
            FixedUpdate,
            apply_player_movement.run_if(in_state(GameState::Playing)),
        );
}

#[derive(Debug, Component)]
pub struct Player;

#[derive(Debug, Component, Deref, DerefMut)]
pub struct CameraSensitivity(Vec2);
impl Default for CameraSensitivity {
    fn default() -> Self {
        Self(Vec2::new(0.003, 0.002))
    }
}

#[derive(Debug, Component)]
struct WorldModelCamera;

#[derive(Debug, Component, Default)]
pub struct PlayerInput {
    movement_direction: Vec3,
}

/// used by the view model camera and the player's arm.
const STATIC_LAYER: usize = 1;

pub fn init_player(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let arm = meshes.add(Cuboid::new(0.1, 0.1, 0.5));
    let arm_material = materials.add(Color::WHITE);

    commands
        .spawn((
            Player,
            PlayerAction::default(),
            CameraSensitivity::default(),
            PlayerInput::default(),
            toolbar::Item::none(),
            // rapier
            RigidBody::Dynamic,
            Collider::capsule(Vec3::new(0.0, -0.5, 0.0), Vec3::new(0.0, 0.5, 0.0), 0.5),
            Velocity::zero(),
            LockedAxes::ROTATION_LOCKED,
            Damping {
                linear_damping: 6.0,
                angular_damping: 1.0,
            },
            GravityScale(3.0),
            Transform::from_xyz(0.0, 1.0, 0.0),
            GlobalTransform::default(),
            InheritedVisibility::default(),
            ViewVisibility::default(),
        ))
        .with_children(|parent| {
            parent.spawn((
                WorldModelCamera,
                Camera3d::default(),
                Projection::from(PerspectiveProjection {
                    fov: 90.0_f32.to_radians(),
                    ..default()
                }),
                Transform::default(),
                GlobalTransform::default(),
                Visibility::default(),
                InheritedVisibility::default(),
                ViewVisibility::default(),
            ));

            // camera voor pitslampke
            parent.spawn((
                Camera3d::default(),
                Camera {
                    order: 1,
                    ..default()
                },
                Projection::from(PerspectiveProjection {
                    fov: 70.0_f32.to_radians(),
                    ..default()
                }),
                RenderLayers::layer(STATIC_LAYER),
                Transform::default(),
                GlobalTransform::default(),
                Visibility::default(),
                InheritedVisibility::default(),
                ViewVisibility::default(),
            ));

            // pitslampke
            parent.spawn((
                Mesh3d(arm),
                MeshMaterial3d(arm_material),
                Transform::from_xyz(0.2, -0.1, -0.25),
                GlobalTransform::default(),
                RenderLayers::layer(STATIC_LAYER),
                NotShadowCaster,
                Visibility::default(),
                InheritedVisibility::default(),
                ViewVisibility::default(),
            ));
        });
}

fn hide_cursor(mut windows: Query<&mut Window, With<PrimaryWindow>>) {
    for mut window in windows.iter_mut() {
        window.cursor_options.visible = false;
    }
}

const PITCH_LIMIT: f32 = std::f32::consts::FRAC_PI_2 - 0.01;

pub fn move_camera(
    accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
    mut player: Query<(&mut Transform, &CameraSensitivity), With<Player>>,
) {
    let Ok((mut transform, camera_sensitivity)) = player.get_single_mut() else {
        return;
    };
    let delta = accumulated_mouse_motion.delta;

    if delta != Vec2::ZERO {
        let delta_yaw = -delta.x * camera_sensitivity.x;
        let delta_pitch = -delta.y * camera_sensitivity.y;

        let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ);
        let yaw = yaw + delta_yaw;
        let pitch = (pitch + delta_pitch).clamp(-PITCH_LIMIT, PITCH_LIMIT);
        transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll);
    }
}

#[derive(Component, Debug, Clone, Copy, Eq, PartialEq, Hash, Reflect, Default)]
pub(crate) enum PlayerAction {
    #[default]
    Move,
    Sprint,
    Jump,
    Interact,
}

pub fn handle_input(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut query: Query<(&Transform, &mut PlayerInput, &mut PlayerAction), With<Player>>,
) {
    for (transform, mut input, mut action) in query.iter_mut() {
        let forward = transform.forward();
        let right = transform.right();
        let mut movement_direction = Vec3::ZERO;

        if keyboard_input.pressed(KeyCode::KeyW) {
            movement_direction += Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero();
        }
        if keyboard_input.pressed(KeyCode::KeyS) {
            movement_direction -= Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero();
        }
        if keyboard_input.pressed(KeyCode::KeyA) {
            movement_direction -= Vec3::new(right.x, 0.0, right.z).normalize_or_zero();
        }
        if keyboard_input.pressed(KeyCode::KeyD) {
            movement_direction += Vec3::new(right.x, 0.0, right.z).normalize_or_zero();
        }
        if keyboard_input.pressed(KeyCode::Space) {
            movement_direction += Vec3::Y;
        }
        if keyboard_input.pressed(KeyCode::ShiftLeft) {
            movement_direction -= Vec3::Y;
        }
        if keyboard_input.pressed(KeyCode::KeyE) {
            *action = PlayerAction::Interact
        }

        input.movement_direction = movement_direction.normalize_or_zero();
    }
}

pub fn apply_player_movement(mut player_query: Query<(&PlayerInput, &mut Velocity), With<Player>>) {
    const SPEED: f32 = 3.0;
    const JUMP_FORCE: f32 = 4.0;

    for (input, mut velocity) in player_query.iter_mut() {
        let horizontal_movement = Vec3::new(
            input.movement_direction.x * SPEED,
            0.0,
            input.movement_direction.z * SPEED,
        );

        velocity.linvel.x = horizontal_movement.x;
        velocity.linvel.z = horizontal_movement.z;

        if input.movement_direction.y > 0.0 {
            velocity.linvel.y = JUMP_FORCE;
        } else if input.movement_direction.y < 0.0 {
            velocity.linvel.y = -SPEED;
        }
    }
}
