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

use crate::types::Rect;
use crate::utils::math::round_to_even;
pub struct Camera {
    // Camera position
    pos: Vec2,
    znear: f32,
    zfar: f32,
    logical_size: Vec2,
    snap_to_px: bool,
    zoom: f32,
}

impl Default for Camera {
    fn default() -> Self {
        Self {
            pos: Vec2::ZERO,
            zfar: 1000.0,
            znear: -1000.0,
            logical_size: Vec2::ZERO,
            snap_to_px: true,
            zoom: 1.0,
        }
    }
}

impl Camera {
    pub fn build_view_projection_matrix(&self) -> Mat4 {
        let size = self.viewport().size();
        let pos = self.pos - size * 0.5;
        let view = Mat4::look_at_rh(pos.extend(1.0), pos.extend(0.0), Vec3::Y);
        let proj = Mat4::orthographic_rh(0.0, size.x, 0.0, size.y, self.znear, self.zfar);
        proj * view
    }

    pub fn viewport(&self) -> Rect {
        let scaled_size = round_to_even(self.logical_size / self.zoom);
        let hf_size = scaled_size * 0.5;
        let min = self.pos - hf_size;
        let max = self.pos + hf_size;
        Rect { min, max }
    }

    pub fn logical_size(&self) -> Vec2 {
        round_to_even(self.logical_size / self.zoom)
    }

    /// Viewport position to world position
    pub fn viewport_to_world(&self, pos: Vec2) -> Vec2 {
        round_to_even((pos / self.zoom) + self.pos)
    }

    /// World position to viewport position
    pub fn world_to_viewport(&self, wpos: Vec2) -> Vec2 {
        round_to_even((wpos - self.pos) * self.zoom)
    }

    pub fn snap_to_px(&self) -> bool {
        self.snap_to_px
    }

    pub fn set_snap_to_px(&mut self, snap_to_px: bool) {
        self.snap_to_px = snap_to_px;
        self.set_pos(self.pos);
    }

    pub fn pos(&self) -> Vec2 {
        self.pos
    }

    pub fn set_pos(&mut self, pos: Vec2) {
        if self.snap_to_px {
            self.pos = pos.round();
        } else {
            self.pos = pos;
        }
    }

    pub(crate) fn resize(&mut self, logical_size: Vec2) {
        self.logical_size = logical_size;
    }

    /// Get the current zoom level
    pub fn zoom(&self) -> f32 {
        self.zoom
    }

    /// Set the zoom level
    pub fn set_zoom(&mut self, zoom: f32) {
        self.zoom = zoom.max(0.01);
    }
}
