pub use crate::ecs::camera::commands::*;
pub use crate::ecs::camera::queries::*;

use crate::ecs::camera::components::compute_pan_orbit_transform;
#[cfg(feature = "gamepad")]
use crate::ecs::input::queries::query_active_gamepad;
use crate::ecs::transform::commands::mark_local_transform_dirty;
use crate::ecs::world::{
    PAN_ORBIT_CAMERA, Vec2, Vec3, World,
    components::{Camera, Projection, Smoothing},
    resources::MouseState,
};

pub fn update_camera_aspect_ratios(world: &mut World) {
    let _span = tracing::info_span!("camera_aspect").entered();
    let window_handle = match &world.resources.window.handle {
        Some(handle) => handle,
        None => return,
    };

    #[cfg(target_arch = "wasm32")]
    let current_size = {
        use winit::platform::web::WindowExtWebSys;
        if let Some(canvas) = window_handle.canvas() {
            (canvas.client_width() as u32, canvas.client_height() as u32)
        } else {
            let inner_size = window_handle.inner_size();
            (inner_size.width, inner_size.height)
        }
    };

    #[cfg(not(target_arch = "wasm32"))]
    let current_size = {
        let inner_size = window_handle.inner_size();
        (inner_size.width, inner_size.height)
    };

    let cached_size = world.resources.window.cached_viewport_size;

    if cached_size == Some(current_size) {
        return;
    }

    world.resources.window.cached_viewport_size = Some(current_size);

    let aspect_ratio = current_size.0 as f32 / current_size.1.max(1) as f32;

    let camera_entities: Vec<_> = world.query_entities(crate::ecs::world::CAMERA).collect();

    for entity in camera_entities {
        let Some(camera) = world.get_camera_mut(entity) else {
            continue;
        };

        if let Projection::Perspective(perspective) = &mut camera.projection {
            perspective.aspect_ratio = Some(aspect_ratio);
        }
    }
}

pub fn fly_camera_system(world: &mut World) {
    look_camera_system(world);
    wasd_keyboard_controls_system(world);
    #[cfg(feature = "gamepad")]
    gamepad_fly_camera_system(world);
    touch_fly_camera_system(world);
}

fn look_camera_system(world: &mut World) {
    use std::sync::atomic::{AtomicBool, Ordering};
    static CAMERA_HAS_CURSOR: AtomicBool = AtomicBool::new(false);

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    let delta_time = world.resources.window.timing.delta_time;

    let (right, up) = {
        let Some(local_transform) = world.get_local_transform(camera_entity) else {
            return;
        };
        (local_transform.right_vector(), local_transform.up_vector())
    };

    let right_clicked = world
        .resources
        .input
        .mouse
        .state
        .contains(MouseState::RIGHT_CLICKED);

    if right_clicked {
        let already_grabbed = CAMERA_HAS_CURSOR.load(Ordering::Relaxed);

        if !already_grabbed {
            if let Some(window_handle) = &world.resources.window.handle {
                if window_handle
                    .set_cursor_grab(winit::window::CursorGrabMode::Locked)
                    .is_err()
                {
                    let _ = window_handle.set_cursor_grab(winit::window::CursorGrabMode::Confined);
                }
                window_handle.set_cursor_visible(false);
            }
            CAMERA_HAS_CURSOR.store(true, Ordering::Relaxed);
        }

        let raw_delta = world.resources.input.mouse.raw_mouse_delta;

        let Some(camera) = world.get_camera_mut(camera_entity) else {
            return;
        };
        let Some(smoothing) = camera.smoothing.as_mut() else {
            return;
        };

        let smoothing_factor = if smoothing.mouse_smoothness > 0.0 {
            1.0 - smoothing.mouse_smoothness.powi(7).powf(delta_time)
        } else {
            1.0
        };
        smoothing.smoothed_mouse_delta = smoothing.smoothed_mouse_delta * (1.0 - smoothing_factor)
            + raw_delta * smoothing_factor;

        let pixels_to_radians = (std::f32::consts::PI / 1000.0) * smoothing.mouse_dpi_scale;
        let mut delta =
            smoothing.smoothed_mouse_delta * smoothing.mouse_sensitivity * pixels_to_radians;
        delta.x *= -1.0;
        delta.y *= -1.0;

        let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
            return;
        };

        let yaw = nalgebra_glm::quat_angle_axis(delta.x, &Vec3::y());
        local_transform.rotation = yaw * local_transform.rotation;

        let forward = local_transform.forward_vector();
        let current_pitch = forward.y.asin();

        let new_pitch = current_pitch + delta.y;
        if new_pitch.abs() <= 89_f32.to_radians() {
            let pitch = nalgebra_glm::quat_angle_axis(delta.y, &Vec3::x());
            local_transform.rotation *= pitch;
        }

        mark_local_transform_dirty(world, camera_entity);
    } else {
        if CAMERA_HAS_CURSOR.load(Ordering::Relaxed) {
            if let Some(window_handle) = &world.resources.window.handle {
                let _ = window_handle.set_cursor_grab(winit::window::CursorGrabMode::None);
                window_handle.set_cursor_visible(true);
            }
            CAMERA_HAS_CURSOR.store(false, Ordering::Relaxed);
        }

        if let Some(Camera {
            smoothing:
                Some(Smoothing {
                    smoothed_mouse_delta,
                    mouse_smoothness,
                    ..
                }),
            ..
        }) = world.get_camera_mut(camera_entity)
        {
            let decay_smoothness = (*mouse_smoothness * 0.5).max(0.01);
            let smoothing_factor = 1.0 - decay_smoothness.powi(7).powf(delta_time);
            *smoothed_mouse_delta =
                *smoothed_mouse_delta * (1.0 - smoothing_factor) + Vec2::zeros() * smoothing_factor;
        }
    }

    if world
        .resources
        .input
        .mouse
        .state
        .contains(MouseState::MIDDLE_CLICKED)
    {
        let mut delta =
            world.resources.input.mouse.position_delta * world.resources.window.timing.delta_time;
        delta.x *= -1.0;
        delta.y *= -1.0;

        let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
            return;
        };
        let translation_right = right * delta.x;
        let translation_up = up * delta.y;

        local_transform.translation += translation_right;
        local_transform.translation += translation_up;

        let changed = translation_right.magnitude() > 0.0 || translation_up.magnitude() > 0.0;
        if changed {
            mark_local_transform_dirty(world, camera_entity);
        }
    }
}

fn wasd_keyboard_controls_system(world: &mut World) {
    #[cfg(feature = "egui")]
    if let Some(gui_state) = &mut world.resources.user_interface.state
        && gui_state.egui_ctx().wants_keyboard_input()
    {
        return;
    }

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };
    let delta_time = world.resources.window.timing.delta_time;

    let (
        left_key_pressed,
        right_key_pressed,
        forward_key_pressed,
        backward_key_pressed,
        up_key_pressed,
        shift_pressed,
    ) = {
        let keyboard = &world.resources.input.keyboard;
        (
            keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyA),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyD),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyW),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyS),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::Space),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::ShiftLeft)
                || keyboard.is_key_pressed(winit::keyboard::KeyCode::ShiftRight),
        )
    };

    let base_speed = if shift_pressed { 60.0 } else { 20.0 };

    let mut target_movement = Vec3::zeros();

    if forward_key_pressed {
        target_movement.z += 1.0;
    }
    if backward_key_pressed {
        target_movement.z -= 1.0;
    }
    if left_key_pressed {
        target_movement.x -= 1.0;
    }
    if right_key_pressed {
        target_movement.x += 1.0;
    }
    if up_key_pressed {
        target_movement.y += 1.0;
    }

    if target_movement.magnitude() > 0.0 {
        target_movement = target_movement.normalize();
    }

    let Some(camera) = world.get_camera_mut(camera_entity) else {
        return;
    };
    let Some(smoothing) = camera.smoothing.as_mut() else {
        return;
    };

    let smoothing_factor = if smoothing.keyboard_smoothness > 0.0 {
        1.0 - smoothing.keyboard_smoothness.powi(7).powf(delta_time)
    } else {
        1.0
    };
    smoothing.smoothed_movement =
        smoothing.smoothed_movement * (1.0 - smoothing_factor) + target_movement * smoothing_factor;

    let movement = smoothing.smoothed_movement;

    let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
        return;
    };
    let forward = local_transform.forward_vector();
    let right = local_transform.right_vector();
    let up = local_transform.up_vector();

    let forward_translation = forward * movement.z * base_speed * delta_time;
    let right_translation = right * movement.x * base_speed * delta_time;
    let up_translation = up * movement.y * base_speed * delta_time;

    local_transform.translation += forward_translation;
    local_transform.translation += right_translation;
    local_transform.translation += up_translation;

    let changed = forward_translation.magnitude() > 0.0
        || right_translation.magnitude() > 0.0
        || up_translation.magnitude() > 0.0;

    if changed {
        mark_local_transform_dirty(world, camera_entity);
    }
}

#[cfg(feature = "gamepad")]
fn apply_deadzone(value: f32, deadzone: f32) -> f32 {
    if value.abs() > deadzone {
        let sign = value.signum();
        let compensated = (value.abs() - deadzone) / (1.0 - deadzone);
        sign * compensated
    } else {
        0.0
    }
}

#[cfg(feature = "gamepad")]
fn gamepad_fly_camera_system(world: &mut World) {
    if world.resources.input.gamepad.gilrs.is_none() {
        return;
    }

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    let delta_time = world.resources.window.timing.delta_time;
    let base_speed = 20.0;

    let (
        left_trigger2_pressed,
        right_trigger2_pressed,
        left_stick_x_axis_data,
        left_stick_y_axis_data,
        right_stick_x_axis_data,
        right_stick_y_axis_data,
        select_pressed,
    ) = {
        let Some(gamepad) = query_active_gamepad(world) else {
            return;
        };
        (
            gamepad.is_pressed(gilrs::Button::LeftTrigger2),
            gamepad.is_pressed(gilrs::Button::RightTrigger2),
            gamepad.axis_data(gilrs::Axis::LeftStickX).cloned(),
            gamepad.axis_data(gilrs::Axis::LeftStickY).cloned(),
            gamepad.axis_data(gilrs::Axis::RightStickX).cloned(),
            gamepad.axis_data(gilrs::Axis::RightStickY).cloned(),
            gamepad.is_pressed(gilrs::Button::Select),
        )
    };

    if select_pressed {
        world.resources.window.should_exit = true;
    }

    let Some(camera) = world.get_camera_mut(camera_entity) else {
        return;
    };
    let Some(smoothing) = camera.smoothing.as_mut() else {
        return;
    };

    let deadzone = smoothing.gamepad_deadzone;

    let mut target_rotation = Vec2::zeros();
    if let Some(axis_data) = right_stick_x_axis_data {
        target_rotation.x = apply_deadzone(axis_data.value(), deadzone);
    }
    if let Some(axis_data) = right_stick_y_axis_data {
        target_rotation.y = apply_deadzone(axis_data.value(), deadzone);
    }

    let smoothing_factor = if smoothing.gamepad_smoothness > 0.0 {
        1.0 - smoothing.gamepad_smoothness.powi(7).powf(delta_time)
    } else {
        1.0
    };
    smoothing.smoothed_gamepad_input = smoothing.smoothed_gamepad_input * (1.0 - smoothing_factor)
        + target_rotation * smoothing_factor;

    let rotation_speed = smoothing.smoothed_gamepad_input * smoothing.gamepad_sensitivity * 2.0;

    let Some(transform) = world.get_local_transform_mut(camera_entity) else {
        return;
    };

    let forward = transform.forward_vector();
    let right = transform.right_vector();
    let up = transform.up_vector();

    let mut changed = false;

    #[cfg(target_arch = "wasm32")]
    let (up_trigger_pressed, down_trigger_pressed) =
        (left_trigger2_pressed, right_trigger2_pressed);
    #[cfg(not(target_arch = "wasm32"))]
    let (up_trigger_pressed, down_trigger_pressed) =
        (right_trigger2_pressed, left_trigger2_pressed);

    if up_trigger_pressed {
        let translation = up * base_speed * delta_time;
        changed |= translation.magnitude() > 0.0;
        transform.translation += translation;
    }

    if down_trigger_pressed {
        let translation = up * base_speed * delta_time;
        changed |= translation.magnitude() > 0.0;
        transform.translation -= translation;
    }

    if let Some(axis_data) = left_stick_x_axis_data {
        let compensated_value = apply_deadzone(axis_data.value(), deadzone);
        let translation = right * compensated_value * base_speed * delta_time;
        changed |= translation.magnitude() > 0.0;
        transform.translation += translation;
    }

    if let Some(axis_data) = left_stick_y_axis_data {
        let compensated_value = apply_deadzone(axis_data.value(), deadzone);
        let translation = forward * compensated_value * base_speed * delta_time;
        changed |= translation.magnitude() > 0.0;
        transform.translation += translation;
    }

    if rotation_speed.x.abs() > 0.001 {
        let yaw = nalgebra_glm::quat_angle_axis(-(rotation_speed.x * delta_time), &Vec3::y());
        transform.rotation = yaw * transform.rotation;
        changed = true;
    }

    if rotation_speed.y.abs() > 0.001 {
        let forward = transform.forward_vector();
        let current_pitch = forward.y.asin();
        let new_pitch = current_pitch + rotation_speed.y * delta_time;
        if new_pitch.abs() <= 89_f32.to_radians() {
            let pitch = nalgebra_glm::quat_angle_axis(rotation_speed.y * delta_time, &Vec3::x());
            transform.rotation *= pitch;
        }
        changed = true;
    }

    if changed {
        mark_local_transform_dirty(world, camera_entity);
    }
}

fn touch_fly_camera_system(world: &mut World) {
    use crate::ecs::input::resources::TouchGesture;

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    #[cfg(feature = "egui")]
    if let Some(gui_state) = &world.resources.user_interface.state
        && gui_state.egui_ctx().wants_pointer_input()
    {
        return;
    }

    let gesture = world.resources.input.touch.gesture;

    let delta_time = world.resources.window.timing.delta_time;
    let base_speed = 20.0;

    match gesture {
        TouchGesture::SingleDrag { delta } => {
            let pixels_to_radians = std::f32::consts::PI / 500.0;
            let rotation_delta = delta * pixels_to_radians;

            let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
                return;
            };

            let yaw = nalgebra_glm::quat_angle_axis(-rotation_delta.x, &Vec3::y());
            local_transform.rotation = yaw * local_transform.rotation;

            let forward = local_transform.forward_vector();
            let current_pitch = forward.y.asin();

            let new_pitch = current_pitch - rotation_delta.y;
            if new_pitch.abs() <= 89_f32.to_radians() {
                let pitch = nalgebra_glm::quat_angle_axis(-rotation_delta.y, &Vec3::x());
                local_transform.rotation *= pitch;
            }

            mark_local_transform_dirty(world, camera_entity);
        }
        TouchGesture::TwoFingerDrag { delta } => {
            let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
                return;
            };

            let right = local_transform.right_vector();
            let up = local_transform.up_vector();

            let pan_scale = delta_time * base_speed * 0.1;
            let translation_right = right * -delta.x * pan_scale;
            let translation_up = up * delta.y * pan_scale;

            local_transform.translation += translation_right;
            local_transform.translation += translation_up;

            let changed = translation_right.magnitude() > 0.0 || translation_up.magnitude() > 0.0;
            if changed {
                mark_local_transform_dirty(world, camera_entity);
            }
        }
        TouchGesture::Pinch { delta, .. } => {
            let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
                return;
            };

            let forward = local_transform.forward_vector();
            let move_speed = delta * delta_time * base_speed * 0.05;
            local_transform.translation += forward * move_speed;

            if move_speed.abs() > 0.0 {
                mark_local_transform_dirty(world, camera_entity);
            }
        }
        TouchGesture::None => {}
    }
}

pub fn pan_orbit_camera_system(world: &mut World) {
    pan_orbit_mouse_input(world);
    #[cfg(feature = "gamepad")]
    pan_orbit_gamepad_input(world);
    pan_orbit_touch_input(world);
    pan_orbit_update_transform(world);
}

fn pan_orbit_mouse_input(world: &mut World) {
    use crate::ecs::camera::components::{PanOrbitButton, PanOrbitModifier};

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    if !world.entity_has_components(camera_entity, PAN_ORBIT_CAMERA) {
        return;
    }

    let mouse_in_viewport =
        if let Some(viewport_rect) = &world.resources.window.active_viewport_rect {
            let mouse_pos = world.resources.input.mouse.position;
            viewport_rect.contains(mouse_pos)
        } else {
            true
        };

    if !mouse_in_viewport {
        return;
    }

    #[cfg(feature = "egui")]
    if let Some(gui_state) = &world.resources.user_interface.state
        && gui_state.egui_ctx().wants_pointer_input()
        && world.resources.window.active_viewport_rect.is_none()
    {
        return;
    }

    if world.resources.user_interface.hud_wants_pointer {
        return;
    }

    let mouse_state = world.resources.input.mouse.state;
    let position_delta = world.resources.input.mouse.position_delta;
    let wheel_delta = world.resources.input.mouse.wheel_delta;

    let window_size = if let Some(window_handle) = &world.resources.window.handle {
        let size = window_handle.inner_size();
        Vec2::new(size.width as f32, size.height as f32)
    } else {
        Vec2::new(1920.0, 1080.0)
    };

    let left_clicked = mouse_state.contains(MouseState::LEFT_CLICKED);
    let right_clicked = mouse_state.contains(MouseState::RIGHT_CLICKED);
    let middle_clicked = mouse_state.contains(MouseState::MIDDLE_CLICKED);
    let scrolled = mouse_state.contains(MouseState::SCROLLED);

    let (shift_pressed, ctrl_pressed, alt_pressed) = {
        let keyboard = &world.resources.input.keyboard;
        (
            keyboard.is_key_pressed(winit::keyboard::KeyCode::ShiftLeft)
                || keyboard.is_key_pressed(winit::keyboard::KeyCode::ShiftRight),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::ControlLeft)
                || keyboard.is_key_pressed(winit::keyboard::KeyCode::ControlRight),
            keyboard.is_key_pressed(winit::keyboard::KeyCode::AltLeft)
                || keyboard.is_key_pressed(winit::keyboard::KeyCode::AltRight),
        )
    };

    let Some(pan_orbit) = world.get_pan_orbit_camera_mut(camera_entity) else {
        return;
    };

    if !pan_orbit.enabled {
        return;
    }

    let is_button_pressed = |button: PanOrbitButton| -> bool {
        match button {
            PanOrbitButton::Left => left_clicked,
            PanOrbitButton::Right => right_clicked,
            PanOrbitButton::Middle => middle_clicked,
        }
    };

    let is_modifier_satisfied = |modifier: Option<PanOrbitModifier>| -> bool {
        match modifier {
            None => true,
            Some(PanOrbitModifier::Shift) => shift_pressed,
            Some(PanOrbitModifier::Control) => ctrl_pressed,
            Some(PanOrbitModifier::Alt) => alt_pressed,
        }
    };

    let orbit_modifier_active = is_modifier_satisfied(pan_orbit.modifier_orbit);
    let pan_modifier_active = is_modifier_satisfied(pan_orbit.modifier_pan);

    let orbit_button_pressed = is_button_pressed(pan_orbit.button_orbit);
    let pan_button_pressed = is_button_pressed(pan_orbit.button_pan);

    let wants_zoom_drag = ctrl_pressed && middle_clicked;
    let wants_orbit = orbit_modifier_active
        && orbit_button_pressed
        && !wants_zoom_drag
        && !(pan_orbit.modifier_pan.is_some() && pan_modifier_active);
    let wants_pan = pan_modifier_active
        && pan_button_pressed
        && !wants_zoom_drag
        && !(pan_orbit.modifier_orbit.is_some() && orbit_modifier_active);

    if wants_zoom_drag {
        let zoom_delta =
            -position_delta.y * pan_orbit.target_radius * 0.01 * pan_orbit.zoom_sensitivity;

        pan_orbit.target_radius += zoom_delta;
        pan_orbit.target_radius = pan_orbit.target_radius.max(pan_orbit.zoom_lower_limit);
        if let Some(upper) = pan_orbit.zoom_upper_limit {
            pan_orbit.target_radius = pan_orbit.target_radius.min(upper);
        }
    }

    if wants_orbit {
        let delta_x = (position_delta.x / window_size.x) * std::f32::consts::PI * 2.0;
        let delta_y = (position_delta.y / window_size.y) * std::f32::consts::PI;

        let delta_x = if pan_orbit.is_upside_down {
            -delta_x
        } else {
            delta_x
        };

        pan_orbit.target_yaw -= delta_x * pan_orbit.orbit_sensitivity;
        pan_orbit.target_pitch += delta_y * pan_orbit.orbit_sensitivity;

        if pan_orbit.allow_upside_down {
            pan_orbit.target_pitch %= std::f32::consts::PI * 2.0;
        } else {
            pan_orbit.target_pitch = pan_orbit
                .target_pitch
                .clamp(pan_orbit.pitch_lower_limit, pan_orbit.pitch_upper_limit);
        }
    }

    if wants_pan {
        let (_, rotation) = compute_pan_orbit_transform(
            pan_orbit.target_focus,
            pan_orbit.target_yaw,
            pan_orbit.target_pitch,
            pan_orbit.target_radius,
        );

        let right = nalgebra_glm::quat_rotate_vec3(&rotation, &Vec3::x());
        let up = nalgebra_glm::quat_rotate_vec3(&rotation, &Vec3::y());

        let pan_scale = pan_orbit.target_radius * pan_orbit.pan_sensitivity * 0.002;
        let pan_right = right * -position_delta.x * pan_scale;
        let pan_up = up * position_delta.y * pan_scale;

        pan_orbit.target_focus += pan_right + pan_up;
    }

    if scrolled {
        let zoom_delta =
            -wheel_delta.y * pan_orbit.target_radius * 0.2 * pan_orbit.zoom_sensitivity;

        pan_orbit.target_radius += zoom_delta;
        pan_orbit.target_radius = pan_orbit.target_radius.max(pan_orbit.zoom_lower_limit);
        if let Some(upper) = pan_orbit.zoom_upper_limit {
            pan_orbit.target_radius = pan_orbit.target_radius.min(upper);
        }
    }

    if !orbit_button_pressed {
        pan_orbit.is_upside_down = pan_orbit.target_pitch.abs() > std::f32::consts::FRAC_PI_2;
    }
}

#[cfg(feature = "gamepad")]
fn pan_orbit_gamepad_input(world: &mut World) {
    if world.resources.input.gamepad.gilrs.is_none() {
        return;
    }

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    if !world.entity_has_components(camera_entity, PAN_ORBIT_CAMERA) {
        return;
    }

    let delta_time = world.resources.window.timing.delta_time;

    let (left_stick_x, left_stick_y, right_stick_x, right_stick_y, left_trigger, right_trigger) = {
        let Some(gamepad) = query_active_gamepad(world) else {
            return;
        };
        (
            gamepad
                .axis_data(gilrs::Axis::LeftStickX)
                .map(|a| a.value())
                .unwrap_or(0.0),
            gamepad
                .axis_data(gilrs::Axis::LeftStickY)
                .map(|a| a.value())
                .unwrap_or(0.0),
            gamepad
                .axis_data(gilrs::Axis::RightStickX)
                .map(|a| a.value())
                .unwrap_or(0.0),
            gamepad
                .axis_data(gilrs::Axis::RightStickY)
                .map(|a| a.value())
                .unwrap_or(0.0),
            if gamepad.is_pressed(gilrs::Button::LeftTrigger2) {
                1.0
            } else {
                0.0
            },
            if gamepad.is_pressed(gilrs::Button::RightTrigger2) {
                1.0
            } else {
                0.0
            },
        )
    };

    let Some(pan_orbit) = world.get_pan_orbit_camera_mut(camera_entity) else {
        return;
    };

    if !pan_orbit.enabled {
        return;
    }

    let deadzone = pan_orbit.gamepad_deadzone;

    let target_orbit = Vec2::new(
        apply_deadzone(right_stick_x, deadzone),
        apply_deadzone(right_stick_y, deadzone),
    );
    let target_pan = Vec2::new(
        apply_deadzone(left_stick_x, deadzone),
        apply_deadzone(left_stick_y, deadzone),
    );

    let smoothing_factor = if pan_orbit.gamepad_smoothness > 0.0 {
        1.0 - pan_orbit.gamepad_smoothness.powi(7).powf(delta_time)
    } else {
        1.0
    };

    pan_orbit.smoothed_gamepad_orbit = pan_orbit.smoothed_gamepad_orbit * (1.0 - smoothing_factor)
        + target_orbit * smoothing_factor;
    pan_orbit.smoothed_gamepad_pan =
        pan_orbit.smoothed_gamepad_pan * (1.0 - smoothing_factor) + target_pan * smoothing_factor;

    let orbit_input = pan_orbit.smoothed_gamepad_orbit;
    let pan_input = pan_orbit.smoothed_gamepad_pan;

    if orbit_input.magnitude() > 0.001 {
        pan_orbit.target_yaw -= orbit_input.x * pan_orbit.gamepad_orbit_sensitivity * delta_time;
        pan_orbit.target_pitch += orbit_input.y * pan_orbit.gamepad_orbit_sensitivity * delta_time;

        pan_orbit.target_pitch = pan_orbit
            .target_pitch
            .clamp(pan_orbit.pitch_lower_limit, pan_orbit.pitch_upper_limit);
    }

    if pan_input.magnitude() > 0.001 {
        let (_, rotation) = compute_pan_orbit_transform(
            pan_orbit.target_focus,
            pan_orbit.target_yaw,
            pan_orbit.target_pitch,
            pan_orbit.target_radius,
        );

        let right = nalgebra_glm::quat_rotate_vec3(&rotation, &Vec3::x());
        let forward_flat = {
            let forward = nalgebra_glm::quat_rotate_vec3(&rotation, &-Vec3::z());
            Vec3::new(forward.x, 0.0, forward.z).normalize()
        };

        let pan_scale =
            pan_orbit.target_radius * pan_orbit.gamepad_pan_sensitivity * delta_time * 0.1;
        pan_orbit.target_focus += right * pan_input.x * pan_scale;
        pan_orbit.target_focus += forward_flat * pan_input.y * pan_scale;
    }

    let zoom_input: f32 = right_trigger - left_trigger;
    if zoom_input.abs() > 0.1 {
        let zoom_delta = zoom_input
            * pan_orbit.target_radius
            * pan_orbit.gamepad_zoom_sensitivity
            * delta_time
            * 0.1;

        pan_orbit.target_radius += zoom_delta;
        pan_orbit.target_radius = pan_orbit.target_radius.max(pan_orbit.zoom_lower_limit);
        if let Some(upper) = pan_orbit.zoom_upper_limit {
            pan_orbit.target_radius = pan_orbit.target_radius.min(upper);
        }
    }
}

fn pan_orbit_touch_input(world: &mut World) {
    use crate::ecs::input::resources::TouchGesture;

    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    if !world.entity_has_components(camera_entity, PAN_ORBIT_CAMERA) {
        return;
    }

    #[cfg(feature = "egui")]
    if let Some(gui_state) = &world.resources.user_interface.state
        && gui_state.egui_ctx().wants_pointer_input()
    {
        return;
    }

    let gesture = world.resources.input.touch.gesture;

    let window_size = if let Some(window_handle) = &world.resources.window.handle {
        let size = window_handle.inner_size();
        Vec2::new(size.width as f32, size.height as f32)
    } else {
        Vec2::new(1920.0, 1080.0)
    };

    let Some(pan_orbit) = world.get_pan_orbit_camera_mut(camera_entity) else {
        return;
    };

    if !pan_orbit.enabled {
        return;
    }

    match gesture {
        TouchGesture::SingleDrag { delta } => {
            let delta_x = delta.x / window_size.x * std::f32::consts::PI * 2.0;
            let delta_y = delta.y / window_size.y * std::f32::consts::PI;

            pan_orbit.target_yaw -= delta_x * pan_orbit.orbit_sensitivity;
            pan_orbit.target_pitch += delta_y * pan_orbit.orbit_sensitivity;

            pan_orbit.target_pitch = pan_orbit
                .target_pitch
                .clamp(pan_orbit.pitch_lower_limit, pan_orbit.pitch_upper_limit);
        }
        TouchGesture::TwoFingerDrag { delta } => {
            let (_, rotation) = compute_pan_orbit_transform(
                pan_orbit.target_focus,
                pan_orbit.target_yaw,
                pan_orbit.target_pitch,
                pan_orbit.target_radius,
            );

            let right = nalgebra_glm::quat_rotate_vec3(&rotation, &Vec3::x());
            let up = nalgebra_glm::quat_rotate_vec3(&rotation, &Vec3::y());

            let pan_scale = pan_orbit.target_radius * pan_orbit.pan_sensitivity * 0.002;
            let pan_right = right * -delta.x * pan_scale;
            let pan_up = up * delta.y * pan_scale;

            pan_orbit.target_focus += pan_right + pan_up;
        }
        TouchGesture::Pinch { delta, .. } => {
            let zoom_delta = -delta * pan_orbit.target_radius * 0.005 * pan_orbit.zoom_sensitivity;

            pan_orbit.target_radius += zoom_delta;
            pan_orbit.target_radius = pan_orbit.target_radius.max(pan_orbit.zoom_lower_limit);
            if let Some(upper) = pan_orbit.zoom_upper_limit {
                pan_orbit.target_radius = pan_orbit.target_radius.min(upper);
            }
        }
        TouchGesture::None => {}
    }
}

fn pan_orbit_update_transform(world: &mut World) {
    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    if !world.entity_has_components(camera_entity, PAN_ORBIT_CAMERA) {
        return;
    }

    let delta_time = world.resources.window.timing.delta_time;

    let Some(pan_orbit) = world.get_pan_orbit_camera_mut(camera_entity) else {
        return;
    };

    if !pan_orbit.enabled {
        return;
    }

    let needs_update = pan_orbit.yaw != pan_orbit.target_yaw
        || pan_orbit.pitch != pan_orbit.target_pitch
        || pan_orbit.radius != pan_orbit.target_radius
        || pan_orbit.focus != pan_orbit.target_focus;

    if !needs_update {
        return;
    }

    let orbit_smoothness = pan_orbit.orbit_smoothness;
    let pan_smoothness = pan_orbit.pan_smoothness;
    let zoom_smoothness = pan_orbit.zoom_smoothness;

    pan_orbit.yaw = lerp_and_snap_f32(
        pan_orbit.yaw,
        pan_orbit.target_yaw,
        orbit_smoothness,
        delta_time,
    );
    pan_orbit.pitch = lerp_and_snap_f32(
        pan_orbit.pitch,
        pan_orbit.target_pitch,
        orbit_smoothness,
        delta_time,
    );
    pan_orbit.radius = lerp_and_snap_f32(
        pan_orbit.radius,
        pan_orbit.target_radius,
        zoom_smoothness,
        delta_time,
    );
    pan_orbit.focus = lerp_and_snap_vec3(
        pan_orbit.focus,
        pan_orbit.target_focus,
        pan_smoothness,
        delta_time,
    );

    let yaw = pan_orbit.yaw;
    let pitch = pan_orbit.pitch;
    let radius = pan_orbit.radius;
    let focus = pan_orbit.focus;

    let (position, rotation) = compute_pan_orbit_transform(focus, yaw, pitch, radius);

    let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
        return;
    };

    local_transform.rotation = rotation;
    local_transform.translation = position;

    mark_local_transform_dirty(world, camera_entity);
}

const SNAP_EPSILON: f32 = 0.001;

fn lerp_and_snap_f32(from: f32, to: f32, smoothness: f32, delta_time: f32) -> f32 {
    let t = smoothness.powi(7);
    let result = from + (to - from) * (1.0 - t.powf(delta_time));
    if smoothness < 1.0 && (result - to).abs() < SNAP_EPSILON {
        to
    } else {
        result
    }
}

fn lerp_and_snap_vec3(from: Vec3, to: Vec3, smoothness: f32, delta_time: f32) -> Vec3 {
    let t = smoothness.powi(7);
    let result = from + (to - from) * (1.0 - t.powf(delta_time));
    if smoothness < 1.0 && (result - to).magnitude() < SNAP_EPSILON {
        to
    } else {
        result
    }
}
