//! First-person camera: mouselook and crouch eye-height easing.

use std::f32::consts::FRAC_PI_2;

use bevy::prelude::*;
use protocol::PlayerInputIntent;
use sim::tuning::{EYE_OFFSET_CROUCH, EYE_OFFSET_STAND};
use sim::{CameraSensitivity, Crouching, Player, PlayerCamera};

const PITCH_LIMIT: f32 = FRAC_PI_2 - 0.01;

/// Applies the presentation-only pitch half of local look intent.
#[unsafe(no_mangle)]
pub fn update_camera_pitch(
    mut intent: Single<&mut PlayerInputIntent, With<Player>>,
    camera: Single<(&mut Transform, &CameraSensitivity), With<PlayerCamera>>,
) {
    let delta_y = intent.look_delta.y;
    if delta_y == 0.0 {
        return;
    }
    intent.look_delta.y = 0.0;

    let (mut camera_transform, sensitivity) = camera.into_inner();

    let (_, pitch, _) = camera_transform.rotation.to_euler(EulerRot::YXZ);
    let new_pitch = (pitch - delta_y * sensitivity.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
    camera_transform.rotation = Quat::from_rotation_x(new_pitch);
}

/// Smoothly eases the camera eye height between standing and crouching.
#[unsafe(no_mangle)]
pub fn update_eye_height(
    time: Res<Time>,
    bodies: Query<&Crouching, With<Player>>,
    mut cameras: Query<(&mut Transform, &ChildOf), With<PlayerCamera>>,
) {
    for (mut transform, child_of) in &mut cameras {
        let crouching = bodies.get(child_of.parent()).map(|c| c.0).unwrap_or(false);
        let target = if crouching {
            EYE_OFFSET_CROUCH
        } else {
            EYE_OFFSET_STAND
        };
        let t = 1.0 - (-16.0 * time.delta_secs()).exp();
        transform.translation.y = transform.translation.y.lerp(target, t);
    }
}
