use serde::{Serialize, Deserialize};
use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum GridType {
    Rectangular,
    Isometric,
    Polar,
    None,
}

impl Default for GridType {
    fn default() -> Self {
        GridType::Rectangular
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum GridSnapStyle {
    Standard,
    Isometric,
}

impl Default for GridSnapStyle {
    fn default() -> Self {
        GridSnapStyle::Standard
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GridSettings {
    pub grid_on: bool,
    pub grid_snap: bool,
    pub grid_type: GridType,
    pub grid_spacing_x: f64,
    pub grid_spacing_y: f64,
    pub grid_spacing_z: f64,
    pub grid_lines: u32,
    pub grid_major_lines: u32,
    pub grid_bounds: (f64, f64, f64, f64),
    pub grid_color: (u8, u8, u8),
    pub grid_axis_color: (u8, u8, u8),
    pub grid_line_weight: i32,
    pub display_grid_beyond_limits: bool,
    pub follow_ucs: bool,
    pub grid_snap_style: GridSnapStyle,
    pub grid_snap_angle: f64,
    pub polar_snap_spacing: f64,
    pub display_polar_tracking_path: bool,
}

impl Default for GridSettings {
    fn default() -> Self {
        Self {
            grid_on: true,
            grid_snap: true,
            grid_type: GridType::Rectangular,
            grid_spacing_x: 1.0,
            grid_spacing_y: 1.0,
            grid_spacing_z: 1.0,
            grid_lines: 100,
            grid_major_lines: 10,
            grid_bounds: (-100.0, -100.0, 100.0, 100.0),
            grid_color: (128, 128, 128),
            grid_axis_color: (255, 0, 0),
            grid_line_weight: 0,
            display_grid_beyond_limits: false,
            follow_ucs: true,
            grid_snap_style: GridSnapStyle::Standard,
            grid_snap_angle: 0.0,
            polar_snap_spacing: 90.0,
            display_polar_tracking_path: true,
        }
    }
}

impl Clone for GridSettings {
    fn clone(&self) -> Self {
        Self {
            grid_on: self.grid_on,
            grid_snap: self.grid_snap,
            grid_type: self.grid_type,
            grid_spacing_x: self.grid_spacing_x,
            grid_spacing_y: self.grid_spacing_y,
            grid_spacing_z: self.grid_spacing_z,
            grid_lines: self.grid_lines,
            grid_major_lines: self.grid_major_lines,
            grid_bounds: self.grid_bounds,
            grid_color: self.grid_color,
            grid_axis_color: self.grid_axis_color,
            grid_line_weight: self.grid_line_weight,
            display_grid_beyond_limits: self.display_grid_beyond_limits,
            follow_ucs: self.follow_ucs,
            grid_snap_style: self.grid_snap_style,
            grid_snap_angle: self.grid_snap_angle,
            polar_snap_spacing: self.polar_snap_spacing,
            display_polar_tracking_path: self.display_polar_tracking_path,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GridPoint {
    pub x: f64,
    pub y: f64,
    pub is_major: bool,
    pub is_axis: bool,
}

impl GridPoint {
    pub fn new(x: f64, y: f64) -> Self {
        Self {
            x,
            y,
            is_major: false,
            is_axis: false,
        }
    }

    pub fn as_major(mut self) -> Self {
        self.is_major = true;
        self
    }

    pub fn as_axis(mut self) -> Self {
        self.is_axis = true;
        self
    }
}

pub struct Grid {
    settings: GridSettings,
    grid_points: Vec<GridPoint>,
    visible_bounds: (f64, f64, f64, f64),
}

impl Default for Grid {
    fn default() -> Self {
        Self {
            settings: GridSettings::default(),
            grid_points: Vec::new(),
            visible_bounds: (-100.0, -100.0, 100.0, 100.0),
        }
    }
}

impl Grid {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn new_with_spacing(spacing_x: f64, spacing_y: f64) -> Self {
        let mut grid = Self::default();
        grid.settings.grid_spacing_x = spacing_x;
        grid.settings.grid_spacing_y = spacing_y;
        grid
    }

    pub fn enable(&mut self, enabled: bool) {
        self.settings.grid_on = enabled;
    }

    pub fn is_enabled(&self) -> bool {
        self.settings.grid_on
    }

    pub fn enable_snap(&mut self, enabled: bool) {
        self.settings.grid_snap = enabled;
    }

    pub fn is_snap_enabled(&self) -> bool {
        self.settings.grid_snap
    }

    pub fn set_spacing(&mut self, x: f64, y: f64) {
        self.settings.grid_spacing_x = x;
        self.settings.grid_spacing_y = y;
    }

    pub fn get_spacing(&self) -> (f64, f64) {
        (self.settings.grid_spacing_x, self.settings.grid_spacing_y)
    }

    pub fn set_major_spacing(&mut self, major: u32) {
        self.settings.grid_major_lines = major;
    }

    pub fn get_major_spacing(&self) -> u32 {
        self.settings.grid_major_lines
    }

    pub fn set_bounds(&mut self, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
        self.settings.grid_bounds = (min_x, min_y, max_x, max_y);
        self.visible_bounds = (min_x, min_y, max_x, max_y);
    }

    pub fn set_visible_bounds(&mut self, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
        self.visible_bounds = (min_x, min_y, max_x, max_y);
    }

    pub fn set_type(&mut self, grid_type: GridType) {
        self.settings.grid_type = grid_type;
    }

    pub fn get_type(&self) -> GridType {
        self.settings.grid_type
    }

    pub fn set_color(&mut self, color: (u8, u8, u8)) {
        self.settings.grid_color = color;
    }

    pub fn set_axis_color(&mut self, color: (u8, u8, u8)) {
        self.settings.grid_axis_color = color;
    }

    pub fn settings(&self) -> &GridSettings {
        &self.settings
    }

    pub fn settings_mut(&mut self) -> &mut GridSettings {
        &mut self.settings
    }

    pub fn generate_grid(&mut self) {
        self.grid_points.clear();

        let (min_x, min_y, max_x, max_y) = self.visible_bounds;
        let spacing_x = self.settings.grid_spacing_x;
        let spacing_y = self.settings.grid_spacing_y;
        let major_spacing = self.settings.grid_major_lines;

        let start_x = (min_x / spacing_x).floor() * spacing_x;
        let start_y = (min_y / spacing_y).floor() * spacing_y;
        let end_x = (max_x / spacing_x).ceil() * spacing_x;
        let end_y = (max_y / spacing_y).ceil() * spacing_y;

        let mut x = start_x;
        let mut x_index = 0;
        while x <= end_x {
            let is_major = x_index % major_spacing == 0;
            let is_axis = x == 0.0;

            let mut y = start_y;
            let mut y_index = 0;
            while y <= end_y {
                let point = GridPoint::new(x, y)
                    .as_major()
                    .is_major(is_major && y_index % major_spacing == 0)
                    .is_axis(is_axis || y == 0.0);

                self.grid_points.push(point);

                y += spacing_y;
                y_index += 1;
            }

            x += spacing_x;
            x_index += 1;
        }
    }

    pub fn get_grid_points(&self) -> &[GridPoint] {
        &self.grid_points
    }

    pub fn snap_to_grid(&self, point: crate::geometry::Point) -> crate::geometry::Point {
        if !self.settings.grid_snap {
            return point;
        }

        let spacing_x = self.settings.grid_spacing_x;
        let spacing_y = self.settings.grid_spacing_y;

        let snapped_x = (point.x / spacing_x).round() * spacing_x;
        let snapped_y = (point.y / spacing_y).round() * spacing_y;

        crate::geometry::Point::new(snapped_x, snapped_y, point.z)
    }

    pub fn get_nearest_grid_point(&self, point: crate::geometry::Point) -> Option<GridPoint> {
        let mut nearest: Option<GridPoint> = None;
        let mut min_distance = f64::MAX;

        for grid_point in &self.grid_points {
            let distance = ((grid_point.x - point.x).powi(2) + (grid_point.y - point.y).powi(2)).sqrt();
            if distance < min_distance {
                min_distance = distance;
                nearest = Some(grid_point.clone());
            }
        }

        if min_distance < self.settings.grid_spacing_x.max(self.settings.grid_spacing_y) {
            nearest
        } else {
            None
        }
    }

    pub fn toggle(&mut self) -> bool {
        self.settings.grid_on = !self.settings.grid_on;
        self.settings.grid_on
    }

    pub fn toggle_snap(&mut self) -> bool {
        self.settings.grid_snap = !self.settings.grid_snap;
        self.settings.grid_snap
    }
}

impl fmt::Display for Grid {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Grid(enabled={}, points={}, spacing=({}, {}))",
            self.settings.grid_on,
            self.grid_points.len(),
            self.settings.grid_spacing_x,
            self.settings.grid_spacing_y
        )
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum OrthoMode {
    Off,
    Horizontal,
    Vertical,
    Both,
}

impl Default for OrthoMode {
    fn default() -> Self {
        OrthoMode::Off
    }
}

pub struct OrthoSettings {
    pub mode: OrthoMode,
    pub ortho_angle: f64,
    pub ortho_top_direction: f64,
    pub display_tool_tip: bool,
}

impl Default for OrthoSettings {
    fn default() -> Self {
        Self {
            mode: OrthoMode::Off,
            ortho_angle: 90.0,
            ortho_top_direction: 90.0,
            display_tool_tip: true,
        }
    }
}

pub struct Ortho {
    settings: OrthoSettings,
}

impl Default for Ortho {
    fn default() -> Self {
        Self {
            settings: OrthoSettings::default(),
        }
    }
}

impl Ortho {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn enable(&mut self, enabled: bool) {
        self.settings.mode = if enabled { OrthoMode::Both } else { OrthoMode::Off };
    }

    pub fn disable(&mut self) {
        self.settings.mode = OrthoMode::Off;
    }

    pub fn is_enabled(&self) -> bool {
        self.settings.mode != OrthoMode::Off
    }

    pub fn set_mode(&mut self, mode: OrthoMode) {
        self.settings.mode = mode;
    }

    pub fn get_mode(&self) -> OrthoMode {
        self.settings.mode
    }

    pub fn toggle(&mut self) -> bool {
        if self.settings.mode == OrthoMode::Off {
            self.settings.mode = OrthoMode::Both;
            true
        } else {
            self.settings.mode = OrthoMode::Off;
            false
        }
    }

    pub fn constrain(
        &self,
        from_point: crate::geometry::Point,
        to_point: crate::geometry::Point,
    ) -> crate::geometry::Point {
        if self.settings.mode == OrthoMode::Off {
            return to_point;
        }

        let dx = to_point.x - from_point.x;
        let dy = to_point.y - from_point.y;

        match self.settings.mode {
            OrthoMode::Horizontal => {
                crate::geometry::Point::new(to_point.x, from_point.y, to_point.z)
            }
            OrthoMode::Vertical => {
                crate::geometry::Point::new(from_point.x, to_point.y, to_point.z)
            }
            OrthoMode::Both => {
                if dx.abs() >= dy.abs() {
                    crate::geometry::Point::new(to_point.x, from_point.y, to_point.z)
                } else {
                    crate::geometry::Point::new(from_point.x, to_point.y, to_point.z)
                }
            }
            OrthoMode::Off => to_point,
        }
    }

    pub fn get_directions(&self) -> Vec<f64> {
        match self.settings.mode {
            OrthoMode::Horizontal => vec![0.0, 180.0],
            OrthoMode::Vertical => vec![90.0, 270.0],
            OrthoMode::Both => vec![0.0, 90.0, 180.0, 270.0],
            OrthoMode::Off => vec![],
        }
    }
}

impl fmt::Display for Ortho {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Ortho(mode={:?})", self.settings.mode)
    }
}

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

    #[test]
    fn test_grid_creation() {
        let grid = Grid::new_with_spacing(1.0, 1.0);
        assert!(grid.is_enabled());
        assert!(grid.is_snap_enabled());
    }

    #[test]
    fn test_grid_snap() {
        let grid = Grid::new_with_spacing(1.0, 1.0);
        let point = crate::geometry::Point::new(1.3, 2.7, 0.0);
        let snapped = grid.snap_to_grid(point);
        assert_eq!(snapped.x, 1.0);
        assert_eq!(snapped.y, 3.0);
    }

    #[test]
    fn test_ortho_constrain() {
        let ortho = Ortho::new();
        let from = crate::geometry::Point::new(0.0, 0.0, 0.0);
        let to = crate::geometry::Point::new(1.0, 1.0, 0.0);

        let constrained = ortho.constrain(from, to);
        assert_eq!(constrained.x, 1.0);
        assert_eq!(constrained.y, 0.0);
    }

    #[test]
    fn test_ortho_toggle() {
        let mut ortho = Ortho::new();
        assert!(!ortho.is_enabled());

        ortho.enable(true);
        assert!(ortho.is_enabled());

        ortho.disable();
        assert!(!ortho.is_enabled());

        assert!(ortho.toggle());
        assert!(ortho.is_enabled());

        assert!(!ortho.toggle());
        assert!(!ortho.is_enabled());
    }
}
