use bevy::{
    input::mouse::AccumulatedMouseMotion, pbr::VolumetricFog, prelude::*, render::view::RenderLayers, window::{PrimaryWindow, WindowResized}
};
use bevy_kira_audio::{Audio, AudioControl};
use bevy_rapier3d::prelude::*;

pub mod toolbar;

use crate::{
    GameState,
    asset_loading::{AudioAssets, FlashlightAssets},
};

#[derive(Debug, Component, Default)]
pub struct Player {
    pub speed_factor: f32,
}

#[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)]
pub struct WorldModelCamera;

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

#[derive(Component, Debug)]
pub struct HeadBob {
    pub enabled: bool,
    pub intensity: f32,
    pub speed: f32,
    pub time_offset: f32,
}

impl Default for HeadBob {
    fn default() -> Self {
        Self {
            enabled: true,
            intensity: 0.05,
            speed: 10.0,
            time_offset: 0.0,
        }
    }
}

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

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

#[derive(Component)]
pub struct FlashlightButtonAnimation {
    pub timer: Timer,
    pub is_pressed: bool,
}

impl Default for FlashlightButtonAnimation {
    fn default() -> Self {
        Self {
            timer: Timer::from_seconds(0.20, TimerMode::Once),
            is_pressed: false,
        }
    }
}

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,
                apply_head_bob,
                on_resize_system,
                handle_flashlight,
                update_flashlight_button_animation,
            )
                .run_if(in_state(GameState::Playing)),
        )
        .add_systems(
            FixedUpdate,
            apply_player_movement.run_if(in_state(GameState::Playing)),
        );
}

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

pub fn init_player(
    mut commands: Commands,
    flashlights: Res<FlashlightAssets>,
    window: Query<&Window>,
) {
    commands
        .spawn((
            Player::default(),
            PlayerAction::default(),
            CameraSensitivity::default(),
            PlayerInput::default(),
            toolbar::Item::none(),
            HeadBob::default(),
            // 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, 0.5, 0.0),
        ))
        .with_children(|parent| {
            parent.spawn((
                WorldModelCamera,
                Camera3d::default(),
                Projection::from(PerspectiveProjection {
                    fov: 90.0_f32.to_radians(),
                    ..default()
                }),
                DistanceFog {
                    color: Color::srgba(0.12, 0.08, 0.08, 0.65), 
                    falloff: FogFalloff::Linear {
                        start: 3.0,  
                        end: 12.0, 
                    },
                    ..default()
                },
            ));

            // camera voor pitslampke
            parent.spawn((
                Camera2d::default(),
                Camera {
                    order: 1,
                    ..default()
                },
                RenderLayers::layer(STATIC_LAYER),
            ));
            let window = window.single();
            let transform = flashlight_base_transform(window.width(), window.height());
            parent.spawn((
                Sprite::from_image(flashlights.flash_hold_4.clone()),
                transform.0.clone(),
                transform,
                RenderLayers::layer(STATIC_LAYER),
                FlashlightButtonAnimation::default(),
            ));

            // feitelijke pitslamp
            parent.spawn((
                Flashlight,
                SpotLight {
                    intensity: 0.0,
                    color: Color::srgba(0.9, 0.628, 0.392, 1.0),
                    range: 5.0,
                    outer_angle: 28.0_f32.to_radians(), // wide cone for flashlight
                    inner_angle: 12.0_f32.to_radians(), // narrower inner cone
                    shadows_enabled: false, // (set to true for realism)
                    radius: 0.35,  // low value for hard light
                    ..default()
                },
                Transform::from_xyz(0.0, -0.15, 0.5),
                GlobalTransform::default(),
            ));
        });
}

fn on_resize_system(
    mut resize_reader: EventReader<WindowResized>,
    mut sprites: Query<(&mut Transform, &mut BaseTransform), With<Sprite>>,
) {
    for e in resize_reader.read() {
        for (mut transform, mut base_transform) in sprites.iter_mut() {
            let new_pos = flashlight_base_transform(e.width, e.height);
            *transform = new_pos.0.clone();
            *base_transform = new_pos;
        }
    }
}

fn flashlight_base_transform(window_width: f32, window_height: f32) -> BaseTransform {
    let window_size = Vec2::new(window_width, window_height);
    let sprite_size = Vec2::new(404.0, 404.0);
    let scale = window_width / 2400.0;
    let world_size = sprite_size * scale;
    let xoffset = window_size.x / 4.0 - 40.0;
    let yoffset = 15.0;

    let mut transform = Transform::from_translation(Vec3::new(
        window_size.x / 2.0 - world_size.x / 2.0 - xoffset,
        -window_size.y / 2.0 + world_size.y / 2.0 - yoffset,
        0.0,
    ));
    transform.scale = Vec3::new(scale, scale, 1.0);
    return BaseTransform(transform);
}

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,
    ToggleFlashlight,
    // Objects and stuff
    PickUp,
    Drop,
}

pub fn handle_input(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut query: Query<(&Transform, &mut PlayerInput, &mut PlayerAction, &mut Player), With<Player>>,
) {
    for (transform, mut input, mut action, mut player) in query.iter_mut() {
        *action = PlayerAction::Move;

        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) {
            player.speed_factor = 1.35;
            *action = PlayerAction::Sprint
        } else if keyboard_input.pressed(KeyCode::ControlLeft) {
            player.speed_factor = 0.65;
        } else {
            player.speed_factor = 1.0;
        }
        if keyboard_input.just_pressed(KeyCode::KeyE) {
            *action = PlayerAction::PickUp;
        }
        if keyboard_input.just_pressed(KeyCode::KeyQ) {
            *action = PlayerAction::Drop;
        }
        if keyboard_input.just_pressed(KeyCode::KeyF) {
            *action = PlayerAction::ToggleFlashlight;
        }

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

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

    for (input, mut velocity, player) in player_query.iter_mut() {
        let speed = SPEED * player.speed_factor;
        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;
        }
    }
}

pub fn apply_head_bob(
    time: Res<Time>,
    mut query: Query<(&PlayerInput, &mut HeadBob, &Player), With<Player>>,
    mut camera_query: Query<&mut Transform, (With<WorldModelCamera>, Without<Player>)>,
    mut sprite_query: Query<
        (&mut Transform, &Sprite, &BaseTransform),
        (With<Sprite>, Without<WorldModelCamera>, Without<Player>),
    >,
) {
    let Ok((input, mut head_bob, player)) = query.get_single_mut() else {
        return;
    };

    // bob when moving horizontally
    let horizontal_movement =
        Vec3::new(input.movement_direction.x, 0.0, input.movement_direction.z);
    let is_moving = horizontal_movement.length_squared() > 0.01;
    let bobbing_speed = head_bob.speed * player.speed_factor;

    let mut offset = bobbing_speed * time.delta_secs();
    if !is_moving {
        // decrease bobbing frequency when stationary
        offset *= 0.2;

        // bob returns to neutral position when stopped
        if let Some(camera_transform) = camera_query.iter_mut().next() {
            let current_offset = camera_transform.translation.y;
            if current_offset.abs() < 0.005 {
                // cancel out so head_bob.time_offset
                offset = -head_bob.time_offset;
            }
        }
    }
    head_bob.time_offset += offset;

    if head_bob.enabled {
        // calculate vertical and horizontal offsets using sine and cosine
        let vertical_offset = head_bob.intensity * f32::sin(head_bob.time_offset);
        let horizontal_offset = (head_bob.intensity * 0.5) * f32::cos(head_bob.time_offset * 0.5);

        // apply
        for mut transform in camera_query.iter_mut() {
            transform.translation.y = vertical_offset;
            if is_moving {
                transform.translation.x = horizontal_offset;
            } else {
                // decrease bobbing magnitude
                transform.translation.x *= 0.8;
            }
        }

        // apply offsets to flashlight
        for (mut transform, _sprite, base_transform) in sprite_query.iter_mut() {
            let scale_factor = 40.0 * player.speed_factor;

            if is_moving {
                transform.translation.x =
                    base_transform.0.translation.x + horizontal_offset * scale_factor;
                transform.translation.y =
                    base_transform.0.translation.y + vertical_offset * scale_factor;

                transform.rotation =
                    Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, horizontal_offset * 0.1);
            } else {
                transform.translation = transform
                    .translation
                    .lerp(base_transform.0.translation, 0.1);
                transform.rotation = transform.rotation.slerp(Quat::IDENTITY, 0.1);
            }
        }
    }
}
pub fn handle_flashlight(
    player_query: Query<&PlayerAction, With<Player>>,
    mut flashlight_query: Query<&mut SpotLight, With<Flashlight>>,
    mut flashlight_sprite_query: Query<&mut FlashlightButtonAnimation>,
    audio_assets: Res<AudioAssets>,
    audio: Res<Audio>,
) {
    let Ok(action) = player_query.get_single() else {
        return;
    };
    if *action != PlayerAction::ToggleFlashlight {
        return;
    }
    if let Ok(mut animation) = flashlight_sprite_query.get_single_mut() {
        animation.is_pressed = true;
        animation.timer.reset();
    }
    if let Ok(mut spotlight) = flashlight_query.get_single_mut() {
        audio.play(audio_assets.flash_click.clone());

        spotlight.intensity = if spotlight.intensity > 0.0 {
            0.0
        } else {
            320_000.0
        };
    }
}

pub fn update_flashlight_button_animation(
    time: Res<Time>,
    mut query: Query<(&mut FlashlightButtonAnimation, &mut Sprite)>,
    flashlights: Res<FlashlightAssets>,
) {
    for (mut animation, mut image) in query.iter_mut() {
        if animation.is_pressed {
            animation.timer.tick(time.delta());
            if animation.timer.finished() {
                *image = Sprite::from_image(flashlights.flash_hold_4.clone());
                animation.is_pressed = false;
            } else {
                *image = Sprite::from_image(flashlights.flash_hold_4_pressed.clone());
            }
        }
    }
}
