use nalgebra_glm::{Vec2, Vec3};

#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PanOrbitCamera {
    pub focus: Vec3,
    pub radius: f32,
    pub yaw: f32,
    pub pitch: f32,
    pub target_focus: Vec3,
    pub target_radius: f32,
    pub target_yaw: f32,
    pub target_pitch: f32,
    pub pitch_upper_limit: f32,
    pub pitch_lower_limit: f32,
    pub zoom_upper_limit: Option<f32>,
    pub zoom_lower_limit: f32,
    pub orbit_sensitivity: f32,
    pub pan_sensitivity: f32,
    pub zoom_sensitivity: f32,
    pub gamepad_orbit_sensitivity: f32,
    pub gamepad_pan_sensitivity: f32,
    pub gamepad_zoom_sensitivity: f32,
    pub gamepad_deadzone: f32,
    pub gamepad_smoothness: f32,
    pub smoothed_gamepad_orbit: Vec2,
    pub smoothed_gamepad_pan: Vec2,
    pub smoothness: f32,
    pub enabled: bool,
}

impl Default for PanOrbitCamera {
    fn default() -> Self {
        Self {
            focus: Vec3::zeros(),
            radius: 10.0,
            yaw: 0.0,
            pitch: 0.0,
            target_focus: Vec3::zeros(),
            target_radius: 10.0,
            target_yaw: 0.0,
            target_pitch: 0.0,
            pitch_upper_limit: 89.0_f32.to_radians(),
            pitch_lower_limit: -89.0_f32.to_radians(),
            zoom_upper_limit: None,
            zoom_lower_limit: 0.01,
            orbit_sensitivity: 1.0,
            pan_sensitivity: 1.0,
            zoom_sensitivity: 1.0,
            gamepad_orbit_sensitivity: 2.0,
            gamepad_pan_sensitivity: 10.0,
            gamepad_zoom_sensitivity: 5.0,
            gamepad_deadzone: 0.15,
            gamepad_smoothness: 0.06,
            smoothed_gamepad_orbit: Vec2::zeros(),
            smoothed_gamepad_pan: Vec2::zeros(),
            smoothness: 0.1,
            enabled: true,
        }
    }
}

impl PanOrbitCamera {
    pub fn new(focus: Vec3, radius: f32) -> Self {
        Self {
            focus,
            radius,
            target_focus: focus,
            target_radius: radius,
            ..Default::default()
        }
    }

    pub fn with_yaw_pitch(mut self, yaw: f32, pitch: f32) -> Self {
        self.yaw = yaw;
        self.pitch = pitch;
        self.target_yaw = yaw;
        self.target_pitch = pitch;
        self
    }

    pub fn with_zoom_limits(mut self, min: f32, max: Option<f32>) -> Self {
        self.zoom_lower_limit = min;
        self.zoom_upper_limit = max;
        self
    }

    pub fn with_pitch_limits(mut self, min: f32, max: f32) -> Self {
        self.pitch_lower_limit = min;
        self.pitch_upper_limit = max;
        self
    }

    pub fn with_smoothness(mut self, smoothness: f32) -> Self {
        self.smoothness = smoothness;
        self
    }

    pub fn compute_camera_transform(&self) -> (Vec3, nalgebra_glm::Quat) {
        compute_pan_orbit_transform(self.focus, self.yaw, self.pitch, self.radius)
    }

    pub fn compute_target_camera_transform(&self) -> (Vec3, nalgebra_glm::Quat) {
        compute_pan_orbit_transform(
            self.target_focus,
            self.target_yaw,
            self.target_pitch,
            self.target_radius,
        )
    }
}

pub fn compute_pan_orbit_transform(
    focus: Vec3,
    yaw: f32,
    pitch: f32,
    radius: f32,
) -> (Vec3, nalgebra_glm::Quat) {
    let yaw_quat = nalgebra_glm::quat_angle_axis(yaw, &Vec3::y());
    let pitch_quat = nalgebra_glm::quat_angle_axis(-pitch, &Vec3::x());
    let rotation = yaw_quat * pitch_quat;
    let position = focus + nalgebra_glm::quat_rotate_vec3(&rotation, &Vec3::new(0.0, 0.0, radius));
    (position, rotation)
}
