// Camera — perspective projection, rotation, zoom.
// Pure glam types, no UI framework dependency.

use glam::{Mat4, Vec2, Vec3};

pub struct Camera {
    pub position: Vec3,
    pub center: Vec3,
    pub aspect_ratio: f32,
    pub zoom_factor: f32,
    pub view_matrix: Mat4,
    pub projection_matrix: Mat4,
    pub is_updated: bool,
    pub near_far: Vec2,
}

impl Default for Camera {
    fn default() -> Self {
        let position = Vec3::new(0.0, 0.0, 10.0);
        let center = Vec3::ZERO;
        let view = Mat4::look_at_rh(position, center, Vec3::Y);
        let projection = Mat4::perspective_rh(60.0_f32.to_radians(), 16.0 / 9.0, 0.01, 100_000.0);

        Self {
            position,
            center,
            aspect_ratio: 16.0 / 9.0,
            zoom_factor: 10.0,
            view_matrix: view,
            projection_matrix: projection,
            is_updated: true,
            near_far: Vec2::new(0.01, 100_000.0),
        }
    }
}

impl Camera {
    pub fn zoom(&mut self, delta: f32) {
        // Proportional zoom: scroll speed scales with distance so zooming
        // feels consistent at any range (close or far).
        let scale = (self.zoom_factor * 0.15).max(0.01);
        self.zoom_factor = (self.zoom_factor + delta * scale).max(0.001);
        let direction = self.position - self.center;
        if direction.length_squared() < f32::EPSILON {
            return;
        }
        let direction = direction.normalize();
        self.position = self.center + direction * self.zoom_factor;
        self.update_view_matrix();
    }

    pub fn rotate(&mut self, dx: f32, dy: f32) {
        self.rotate_scaled(dx, dy, 1.0);
    }

    pub fn rotate_scaled(&mut self, dx: f32, dy: f32, speed: f32) {
        let offset = self.position - self.center;
        if offset.length_squared() < f32::EPSILON {
            return;
        }
        let r = offset.length();
        let s = 0.005 * speed;

        let theta = offset.z.atan2(offset.x) + dx * s;
        let phi = (offset.y / r).clamp(-1.0, 1.0).acos() + dy * s;
        let phi = phi.clamp(0.01, std::f32::consts::PI - 0.01);

        self.position =
            self.center + Vec3::new(r * phi.sin() * theta.cos(), r * phi.cos(), r * phi.sin() * theta.sin());
        self.update_view_matrix();
    }

    pub fn pan(&mut self, dx: f32, dy: f32) {
        self.pan_scaled(dx, dy, 1.0);
    }

    pub fn pan_scaled(&mut self, dx: f32, dy: f32, speed: f32) {
        let forward = self.center - self.position;
        if forward.length_squared() < f32::EPSILON {
            return;
        }
        let forward = forward.normalize();
        let right = forward.cross(Vec3::Y);
        if right.length_squared() < f32::EPSILON {
            return;
        }
        let right = right.normalize();
        let up = right.cross(forward).normalize();

        let s = 0.005 * speed;
        let offset = right * dx * s + up * dy * s;
        self.position += offset;
        self.center += offset;
        self.update_view_matrix();
    }

    pub fn update_projection(&mut self, aspect: f32, fov_degrees: f32) {
        self.aspect_ratio = aspect;
        self.projection_matrix =
            Mat4::perspective_rh(fov_degrees.to_radians(), aspect, self.near_far.x, self.near_far.y);
        self.is_updated = true;
    }

    pub fn update_view_matrix(&mut self) {
        self.view_matrix = Mat4::look_at_rh(self.position, self.center, Vec3::Y);
        self.is_updated = true;
    }

    pub fn set_target(&mut self, target: Vec3) {
        let direction = self.position - self.center;
        self.center = target;
        self.position = target + direction;
        self.update_view_matrix();
    }

    pub fn set_distance(&mut self, distance: f32) {
        self.zoom_factor = distance.max(0.1);
        let direction = self.position - self.center;
        if direction.length_squared() < f32::EPSILON {
            self.position = self.center + Vec3::new(0.0, 0.0, self.zoom_factor);
        } else {
            self.position = self.center + direction.normalize() * self.zoom_factor;
        }
        self.update_view_matrix();
    }

    pub fn reset(&mut self) {
        *self = Self::default();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn camera_default_looks_at_origin() {
        let cam = Camera::default();
        assert_eq!(cam.center, Vec3::ZERO);
        assert!(cam.position.z > 0.0);
    }

    #[test]
    fn zoom_changes_position() {
        let mut cam = Camera::default();
        let old_dist = cam.position.length();
        cam.zoom(1.0);
        let new_dist = cam.position.length();
        assert!(new_dist > old_dist);
    }

    #[test]
    fn reset_restores_default() {
        let mut cam = Camera::default();
        cam.zoom(5.0);
        cam.reset();
        assert_eq!(cam.zoom_factor, 10.0);
    }
}
