use std::f64::consts::PI;
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

impl Point {
    pub fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    pub fn distance_to(&self, other: Point) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }

    pub fn angle_to(&self, other: Point) -> f64 {
        (other.y - self.y).atan2(other.x - self.x)
    }

    pub fn lerp(&self, other: Point, t: f64) -> Point {
        Point {
            x: self.x + (other.x - self.x) * t,
            y: self.y + (other.y - self.y) * t,
        }
    }

    pub fn rotate(&self, center: Point, angle: f64) -> Point {
        let dx = self.x - center.x;
        let dy = self.y - center.y;
        let cos = angle.cos();
        let sin = angle.sin();
        Point {
            x: center.x + dx * cos - dy * sin,
            y: center.y + dx * sin + dy * cos,
        }
    }
}

impl Default for Point {
    fn default() -> Self {
        Self { x: 0.0, y: 0.0 }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Line {
    pub start: Point,
    pub end: Point,
}

impl Line {
    pub fn new(start: Point, end: Point) -> Self {
        Self { start, end }
    }

    pub fn length(&self) -> f64 {
        self.start.distance_to(self.end)
    }

    pub fn angle(&self) -> f64 {
        self.start.angle_to(self.end)
    }
}

#[derive(Debug, Clone)]
pub struct Circle {
    pub center: Point,
    pub radius: f64,
}

impl Circle {
    pub fn new(center: Point, radius: f64) -> Self {
        Self { center, radius }
    }

    pub fn circumference(&self) -> f64 {
        2.0 * PI * self.radius
    }

    pub fn area(&self) -> f64 {
        PI * self.radius.powi(2)
    }
}

#[derive(Debug, Clone)]
pub struct Arc {
    pub center: Point,
    pub radius: f64,
    pub start_angle: f64,
    pub end_angle: f64,
    pub is_counter_clockwise: bool,
}

impl Arc {
    pub fn new(center: Point, radius: f64, start_angle: f64, end_angle: f64) -> Self {
        let is_counter_clockwise = if end_angle >= start_angle {
            end_angle - start_angle <= PI
        } else {
            start_angle - end_angle > PI
        };
        
        Self {
            center,
            radius,
            start_angle,
            end_angle,
            is_counter_clockwise,
        }
    }

    pub fn length(&self) -> f64 {
        let sweep = if self.is_counter_clockwise {
            let sweep = self.end_angle - self.start_angle;
            if sweep < 0.0 { sweep + 2.0 * PI } else { sweep }
        } else {
            let sweep = self.start_angle - self.end_angle;
            if sweep < 0.0 { sweep + 2.0 * PI } else { sweep }
        };
        self.radius * sweep
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ellipse {
    pub center: Point,
    pub semi_major: f64,
    pub semi_minor: f64,
    pub rotation: f64,
}

impl Ellipse {
    pub fn new(center: Point, semi_major: f64, semi_minor: f64, rotation: f64) -> Self {
        Self {
            center,
            semi_major,
            semi_minor,
            rotation,
        }
    }

    pub fn eccentricity(&self) -> f64 {
        (1.0 - (self.semi_minor / self.semi_major).powi(2)).sqrt()
    }

    pub fn area(&self) -> f64 {
        PI * self.semi_major * self.semi_minor
    }

    pub fn circumference_approx(&self) -> f64 {
        let a = self.semi_major;
        let b = self.semi_minor;
        PI * (3.0 * (a + b) - ((3.0 * a + b) * (a + 3.0 * b)).sqrt())
    }
}

#[derive(Debug, Clone)]
pub struct EllipseArc {
    pub center: Point,
    pub semi_major: f64,
    pub semi_minor: f64,
    pub rotation: f64,
    pub start_angle: f64,
    pub end_angle: f64,
    pub is_counter_clockwise: bool,
}

impl EllipseArc {
    pub fn new(
        center: Point,
        semi_major: f64,
        semi_minor: f64,
        rotation: f64,
        start_angle: f64,
        end_angle: f64,
    ) -> Self {
        let is_counter_clockwise = if end_angle >= start_angle {
            end_angle - start_angle <= PI
        } else {
            start_angle - end_angle > PI
        };

        Self {
            center,
            semi_major,
            semi_minor,
            rotation,
            start_angle,
            end_angle,
            is_counter_clockwise,
        }
    }

    pub fn point_at(&self, t: f64) -> Point {
        let angle = if self.is_counter_clockwise {
            self.start_angle + t * (self.end_angle - self.start_angle)
        } else {
            self.start_angle - t * (self.start_angle - self.end_angle)
        };
        
        let local_x = self.semi_major * angle.cos();
        let local_y = self.semi_minor * angle.sin();
        
        let cos_rot = self.rotation.cos();
        let sin_rot = self.rotation.sin();
        
        Point {
            x: self.center.x + local_x * cos_rot - local_y * sin_rot,
            y: self.center.y + local_x * sin_rot + local_y * cos_rot,
        }
    }

    pub fn length_approx(&self, samples: usize) -> f64 {
        let mut length = 0.0;
        let dt = 1.0 / samples as f64;
        
        for i in 0..samples {
            let t1 = i as f64 * dt;
            let t2 = (i + 1) as f64 * dt;
            let p1 = self.point_at(t1);
            let p2 = self.point_at(t2);
            length += p1.distance_to(p2);
        }
        
        length
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Polyline {
    pub vertices: Vec<Point>,
    pub is_closed: bool,
}

impl Polyline {
    pub fn new(vertices: Vec<Point>, is_closed: bool) -> Self {
        Self { vertices, is_closed }
    }

    pub fn length(&self) -> f64 {
        if self.vertices.len() < 2 {
            return 0.0;
        }
        
        let mut length = 0.0;
        for i in 0..self.vertices.len() - 1 {
            length += self.vertices[i].distance_to(self.vertices[i + 1]);
        }
        
        if self.is_closed && self.vertices.len() > 2 {
            length += self.vertices.last().unwrap().distance_to(self.vertices[0]);
        }
        
        length
    }
}

#[derive(Debug, Clone)]
pub struct NurbsCurve {
    pub control_points: Vec<Point>,
    pub degree: usize,
    pub knots: Vec<f64>,
    pub weights: Option<Vec<f64>>,
}

impl NurbsCurve {
    pub fn new(control_points: Vec<Point>, degree: usize) -> Self {
        let n = control_points.len();
        let m = n + degree + 1;
        
        let mut knots = Vec::with_capacity(m);
        for _ in 0..=degree {
            knots.push(0.0);
        }
        for i in degree + 1..n {
            let t = (i - degree) as f64 / (n - degree) as f64;
            knots.push(t);
        }
        for _ in 0..=degree {
            knots.push(1.0);
        }
        
        Self {
            control_points,
            degree,
            knots,
            weights: None,
        }
    }

    pub fn with_weights(mut self, weights: Vec<f64>) -> Self {
        self.weights = Some(weights);
        self
    }

    pub fn point_at(&self, t: f64) -> Point {
        let _ = self.control_points.len() - 1;
        let t = t.clamp(0.0, 1.0);
        
        let span = self.find_span(t);
        let basis = self.evaluate_basis(span, t);
        
        let mut x = 0.0;
        let mut y = 0.0;
        let mut w = 0.0;
        
        for i in 0..=self.degree {
            let index = span - self.degree + i;
            let weight = self.weights.as_ref()
                .map_or(1.0, |w| w[index]);
            let b = basis[i];
            
            x += self.control_points[index].x * weight * b;
            y += self.control_points[index].y * weight * b;
            w += weight * b;
        }
        
        Point { x: x / w, y: y / w }
    }

    fn find_span(&self, t: f64) -> usize {
        let n = self.control_points.len() - 1;
        
        if t >= self.knots[n + 1] {
            return n;
        }
        
        let mut low = self.degree;
        let mut high = n + 1;
        let mut mid = (low + high) / 2;
        
        while t < self.knots[mid] || t >= self.knots[mid + 1] {
            if t < self.knots[mid] {
                high = mid;
            } else {
                low = mid;
            }
            mid = (low + high) / 2;
        }
        
        mid
    }

    fn evaluate_basis(&self, span: usize, t: f64) -> Vec<f64> {
        let p = self.degree;
        let n = p + 1;
        
        let mut left = vec![0.0; n];
        let mut right = vec![0.0; n];
        let mut basis = vec![1.0; n];
        
        for i in 1..=p {
            left[i - 1] = t - self.knots[span + 1 - i];
            right[i - 1] = self.knots[span + i] - t;
            
            let mut saved = 0.0;
            for j in 0..i {
                let temp = basis[j] / (right[j] + left[i - 1 - j]);
                basis[j] = saved + right[j] * temp;
                saved = left[i - 1 - j] * temp;
            }
            basis[i] = saved;
        }
        
        basis
    }
}

#[derive(Debug, Clone)]
pub struct InvoluteCurve {
    pub base_circle_radius: f64,
    pub start_point: Point,
    pub direction: f64,
    pub resolution: usize,
}

impl InvoluteCurve {
    pub fn new(base_circle_radius: f64, start_point: Point, direction: f64) -> Self {
        Self {
            base_circle_radius,
            start_point,
            direction,
            resolution: 100,
        }
    }

    pub fn point_at_angle(&self, angle: f64) -> Point {
        let t = (self.base_circle_radius * angle).cos() + angle * (self.base_circle_radius * angle).sin();
        let y_coord = (self.base_circle_radius * angle).sin() - angle * (self.base_circle_radius * angle).cos();
        
        Point {
            x: self.start_point.x + self.base_circle_radius * (t * self.direction.cos() - y_coord * self.direction.sin()),
            y: self.start_point.y + self.base_circle_radius * (t * self.direction.sin() + y_coord * self.direction.cos()),
        }
    }

    pub fn generate_points(&self, end_angle: f64) -> Vec<Point> {
        let mut points = Vec::with_capacity(self.resolution);
        let step = end_angle / self.resolution as f64;
        
        for i in 0..=self.resolution {
            let angle = i as f64 * step;
            points.push(self.point_at_angle(angle));
        }
        
        points
    }
}

#[derive(Debug, Clone)]
pub struct GearProfile {
    pub num_teeth: usize,
    pub module: f64,
    pub pressure_angle: f64,
    pub addendum_coefficient: f64,
    pub dedendum_coefficient: f64,
    pub fillet_radius: f64,
}

impl GearProfile {
    pub fn new(num_teeth: usize, module: f64, pressure_angle: f64) -> Self {
        Self {
            num_teeth,
            module,
            pressure_angle,
            addendum_coefficient: 1.0,
            dedendum_coefficient: 1.25,
            fillet_radius: 0.38 * module,
        }
    }

    pub fn pitch_radius(&self) -> f64 {
        self.module * self.num_teeth as f64 / 2.0
    }

    pub fn base_radius(&self) -> f64 {
        self.pitch_radius() * self.pressure_angle.cos()
    }

    pub fn addendum_radius(&self) -> f64 {
        self.pitch_radius() + self.addendum_coefficient * self.module
    }

    pub fn dedendum_radius(&self) -> f64 {
        self.pitch_radius() - self.dedendum_coefficient * self.module
    }

    pub fn generate_tooth_profile(&self, tooth_index: usize) -> Vec<Point> {
        let mut profile = Vec::new();
        
        let tooth_angle = 2.0 * PI / self.num_teeth as f64;
        let base_angle = tooth_index as f64 * tooth_angle;
        
        let involute = InvoluteCurve::new(
            self.base_radius(),
            Point::new(self.base_radius(), 0.0),
            0.0,
        );
        
        let start_angle = 0.0;
        let end_angle = PI / 2.0 - self.pressure_angle;
        
        let involute_points = involute.generate_points(end_angle);
        
        let addendum_angle = end_angle;
        
        profile.push(Point::new(
            self.addendum_radius() * (base_angle + addendum_angle).cos(),
            self.addendum_radius() * (base_angle + addendum_angle).sin(),
        ));
        
        for p in &involute_points {
            let angle = base_angle + start_angle + (p.y / self.base_radius());
            let radius = (p.x.powi(2) + p.y.powi(2)).sqrt();
            profile.push(Point::new(
                radius * angle.cos(),
                radius * angle.sin(),
            ));
        }
        
        let _dedendum_start_angle = PI / 2.0 - self.pressure_angle;
        let dedendum_end_angle = PI / 2.0 + self.pressure_angle;
        
        profile.push(Point::new(
            self.dedendum_radius() * (base_angle + dedendum_end_angle).cos(),
            self.dedendum_radius() * (base_angle + dedendum_end_angle).sin(),
        ));
        
        let tooth_center_angle = PI / self.num_teeth as f64;
        let next_tooth_start = base_angle + tooth_center_angle;
        
        profile.push(Point::new(
            self.dedendum_radius() * (next_tooth_start - self.pressure_angle).cos(),
            self.dedendum_radius() * (next_tooth_start - self.pressure_angle).sin(),
        ));
        
        profile
    }

    pub fn generate_full_profile(&self) -> Vec<Point> {
        let mut full_profile = Vec::new();
        
        for i in 0..self.num_teeth {
            let tooth_profile = self.generate_tooth_profile(i);
            full_profile.extend(tooth_profile);
        }
        
        full_profile
    }
}

#[derive(Debug, Clone)]
pub struct Helix2D {
    pub center: Point,
    pub radius: f64,
    pub pitch: f64,
    pub rotation_direction: f64,
}

impl Helix2D {
    pub fn new(center: Point, radius: f64, pitch: f64, rotation_direction: f64) -> Self {
        Self {
            center,
            radius,
            pitch,
            rotation_direction,
        }
    }

    pub fn point_at_z(&self, z: f64) -> Point {
        let angle = z * self.rotation_direction * 2.0 * PI / self.pitch;
        
        Point {
            x: self.center.x + self.radius * angle.cos(),
            y: self.center.y + self.radius * angle.sin(),
        }
    }

    pub fn generate_points(&self, height: f64, resolution: usize) -> Vec<Point> {
        let mut points = Vec::with_capacity(resolution);
        let step = height / resolution as f64;
        
        for i in 0..=resolution {
            points.push(self.point_at_z(i as f64 * step));
        }
        
        points
    }
}

#[derive(Debug, Clone)]
pub struct CloudLine {
    pub points: Vec<Point>,
    pub bulge_factor: f64,
    pub amplitude: f64,
    pub frequency: f64,
}

impl CloudLine {
    pub fn new(points: Vec<Point>, bulge_factor: f64) -> Self {
        Self {
            points,
            bulge_factor,
            amplitude: 2.0,
            frequency: 3.0,
        }
    }

    pub fn with_amplitude(mut self, amplitude: f64) -> Self {
        self.amplitude = amplitude;
        self
    }

    pub fn with_frequency(mut self, frequency: f64) -> Self {
        self.frequency = frequency;
        self
    }

    pub fn generate_points(&self, segments_per_edge: usize) -> Vec<Point> {
        let mut result = Vec::new();
        
        for i in 0..self.points.len() {
            let start = self.points[i];
            let end = self.points[(i + 1) % self.points.len()];
            
            let dx = end.x - start.x;
            let dy = end.y - start.y;
            let length = (dx.powi(2) + dy.powi(2)).sqrt();
            
            if length < 1e-6 {
                continue;
            }
            
            let nx = dx / length;
            let ny = dy / length;
            
            let perpendicular_x = -ny;
            let perpendicular_y = nx;
            
            for j in 0..=segments_per_edge {
                let t = j as f64 / segments_per_edge as f64;
                let base_x = start.x + dx * t;
                let base_y = start.y + dy * t;
                
                let normalized_t = t * PI * self.frequency;
                let bulge = (normalized_t.sin() * self.amplitude * self.bulge_factor)
                    * (1.0 - (t - 0.5).powi(2) * 4.0);
                
                result.push(Point {
                    x: base_x + perpendicular_x * bulge,
                    y: base_y + perpendicular_y * bulge,
                });
            }
        }
        
        result
    }
}

#[derive(Debug, Clone)]
pub struct SplineFittedPolyline {
    pub control_points: Vec<Point>,
    pub tolerance: f64,
}

impl SplineFittedPolyline {
    pub fn new(control_points: Vec<Point>, tolerance: f64) -> Self {
        Self {
            control_points,
            tolerance,
        }
    }

    pub fn fit(&self) -> Vec<Point> {
        if self.control_points.len() < 3 {
            return self.control_points.clone();
        }
        
        let nurbs = NurbsCurve::new(self.control_points.clone(), 3);
        
        let total_length = self.calculate_total_length();
        let num_segments = (total_length / self.tolerance).ceil() as usize;
        
        let mut fitted_points = Vec::with_capacity(num_segments + 1);
        
        for i in 0..=num_segments {
            let t = i as f64 / num_segments as f64;
            fitted_points.push(nurbs.point_at(t));
        }
        
        fitted_points
    }

    fn calculate_total_length(&self) -> f64 {
        let mut length = 0.0;
        for i in 0..self.control_points.len() - 1 {
            length += self.control_points[i].distance_to(self.control_points[i + 1]);
        }
        length
    }
}
