use anyhow::{Context, Result};
use std::path::Path;

#[derive(Debug, Clone, Copy)]
pub struct Vec3 {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

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

    pub fn zero() -> Self {
        Self { x: 0.0, y: 0.0, z: 0.0 }
    }

    pub fn dot(self, other: Self) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }

    pub fn cross(self, other: Self) -> Self {
        Self {
            x: self.y * other.z - self.z * other.y,
            y: self.z * other.x - self.x * other.z,
            z: self.x * other.y - self.y * other.x,
        }
    }

    pub fn length(self) -> f64 {
        self.dot(self).sqrt()
    }

    pub fn normalized(self) -> Self {
        let len = self.length();
        if len < 1e-12 {
            return Self::zero();
        }
        Self { x: self.x / len, y: self.y / len, z: self.z / len }
    }

    pub fn component_min(self, other: Self) -> Self {
        Self {
            x: self.x.min(other.x),
            y: self.y.min(other.y),
            z: self.z.min(other.z),
        }
    }

    pub fn component_max(self, other: Self) -> Self {
        Self {
            x: self.x.max(other.x),
            y: self.y.max(other.y),
            z: self.z.max(other.z),
        }
    }
}

impl std::ops::Add for Vec3 {
    type Output = Self;
    fn add(self, rhs: Self) -> Self {
        Self { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z }
    }
}

impl std::ops::Sub for Vec3 {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self {
        Self { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z }
    }
}

impl std::ops::Mul<f64> for Vec3 {
    type Output = Self;
    fn mul(self, rhs: f64) -> Self {
        Self { x: self.x * rhs, y: self.y * rhs, z: self.z * rhs }
    }
}

impl std::ops::Neg for Vec3 {
    type Output = Self;
    fn neg(self) -> Self {
        Self { x: -self.x, y: -self.y, z: -self.z }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Triangle {
    pub v: [Vec3; 3],
}

impl Triangle {
    pub fn normal(&self) -> Vec3 {
        let e1 = self.v[1] - self.v[0];
        let e2 = self.v[2] - self.v[0];
        e1.cross(e2).normalized()
    }

    pub fn area(&self) -> f64 {
        let e1 = self.v[1] - self.v[0];
        let e2 = self.v[2] - self.v[0];
        e1.cross(e2).length() * 0.5
    }

    pub fn centroid(&self) -> Vec3 {
        Vec3 {
            x: (self.v[0].x + self.v[1].x + self.v[2].x) / 3.0,
            y: (self.v[0].y + self.v[1].y + self.v[2].y) / 3.0,
            z: (self.v[0].z + self.v[1].z + self.v[2].z) / 3.0,
        }
    }

    /// Closest point on triangle to point p, and the unsigned distance.
    pub fn closest_point(&self, p: Vec3) -> (Vec3, f64) {
        let a = self.v[0];
        let b = self.v[1];
        let c = self.v[2];
        let ab = b - a;
        let ac = c - a;
        let ap = p - a;

        let d1 = ab.dot(ap);
        let d2 = ac.dot(ap);
        if d1 <= 0.0 && d2 <= 0.0 {
            return (a, (p - a).length());
        }

        let bp = p - b;
        let d3 = ab.dot(bp);
        let d4 = ac.dot(bp);
        if d3 >= 0.0 && d4 <= d3 {
            return (b, (p - b).length());
        }

        let vc = d1 * d4 - d3 * d2;
        if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
            let v = d1 / (d1 - d3);
            let pt = a + ab * v;
            return (pt, (p - pt).length());
        }

        let cp = p - c;
        let d5 = ab.dot(cp);
        let d6 = ac.dot(cp);
        if d6 >= 0.0 && d5 <= d6 {
            return (c, (p - c).length());
        }

        let vb = d5 * d2 - d1 * d6;
        if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
            let w = d2 / (d2 - d6);
            let pt = a + ac * w;
            return (pt, (p - pt).length());
        }

        let va = d3 * d6 - d5 * d4;
        if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
            let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
            let pt = b + (c - b) * w;
            return (pt, (p - pt).length());
        }

        let denom = 1.0 / (va + vb + vc);
        let v = vb * denom;
        let w = vc * denom;
        let pt = a + ab * v + ac * w;
        (pt, (p - pt).length())
    }
}

#[derive(Debug, Clone, Copy)]
pub struct AABB {
    pub min: Vec3,
    pub max: Vec3,
}

impl AABB {
    pub fn empty() -> Self {
        Self {
            min: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
            max: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
        }
    }

    pub fn from_triangle(tri: &Triangle) -> Self {
        let mut aabb = Self::empty();
        for v in &tri.v {
            aabb.min = aabb.min.component_min(*v);
            aabb.max = aabb.max.component_max(*v);
        }
        aabb
    }

    pub fn union(&self, other: &AABB) -> AABB {
        AABB {
            min: self.min.component_min(other.min),
            max: self.max.component_max(other.max),
        }
    }

    pub fn center(&self) -> Vec3 {
        Vec3 {
            x: (self.min.x + self.max.x) * 0.5,
            y: (self.min.y + self.max.y) * 0.5,
            z: (self.min.z + self.max.z) * 0.5,
        }
    }

    pub fn extent(&self) -> Vec3 {
        self.max - self.min
    }

    pub fn longest_axis(&self) -> usize {
        let e = self.extent();
        if e.x >= e.y && e.x >= e.z { 0 }
        else if e.y >= e.z { 1 }
        else { 2 }
    }

    pub fn distance_to_point(&self, p: Vec3) -> f64 {
        let dx = (self.min.x - p.x).max(0.0).max(p.x - self.max.x);
        let dy = (self.min.y - p.y).max(0.0).max(p.y - self.max.y);
        let dz = (self.min.z - p.z).max(0.0).max(p.z - self.max.z);
        (dx * dx + dy * dy + dz * dz).sqrt()
    }

    pub fn diagonal(&self) -> f64 {
        self.extent().length()
    }
}

pub struct TriangleMesh {
    pub triangles: Vec<Triangle>,
    pub bounds: AABB,
}

impl TriangleMesh {
    /// Remove degenerate triangles: zero/near-zero area, NaN/Inf vertices,
    /// and collapsed edges. Threshold is relative to the mesh diagonal.
    fn sanitize(&mut self) {
        if self.triangles.is_empty() {
            return;
        }
        let diag = self.bounds.diagonal();
        // Minimum area: square of 1e-7 times diagonal — catches slivers from
        // CAD exports without rejecting intentionally thin geometry.
        let min_area = (diag * 1e-7).powi(2) * 0.5;
        // Minimum edge length squared
        let min_edge_sq = (diag * 1e-8).powi(2);

        self.triangles.retain(|tri| {
            // Reject NaN/Inf vertices
            for v in &tri.v {
                if !v.x.is_finite() || !v.y.is_finite() || !v.z.is_finite() {
                    return false;
                }
            }
            // Reject degenerate area
            if tri.area() < min_area {
                return false;
            }
            // Reject collapsed edges (two vertices coincident)
            let e0 = tri.v[1] - tri.v[0];
            let e1 = tri.v[2] - tri.v[1];
            let e2 = tri.v[0] - tri.v[2];
            if e0.dot(e0) < min_edge_sq
                || e1.dot(e1) < min_edge_sq
                || e2.dot(e2) < min_edge_sq
            {
                return false;
            }
            true
        });

        // Recompute bounds after filtering
        self.bounds = AABB::empty();
        for tri in &self.triangles {
            for v in &tri.v {
                self.bounds.min = self.bounds.min.component_min(*v);
                self.bounds.max = self.bounds.max.component_max(*v);
            }
        }
    }

    pub fn from_stl(path: &Path) -> Result<Self> {
        let data = std::fs::read(path)
            .with_context(|| format!("reading {}", path.display()))?;

        if data.len() > 5 && &data[0..5] == b"solid" {
            if let Ok(mesh) = Self::parse_ascii_stl(&data) {
                if !mesh.triangles.is_empty() {
                    return Ok(mesh);
                }
            }
        }

        Self::parse_binary_stl(&data)
    }

    fn parse_binary_stl(data: &[u8]) -> Result<Self> {
        anyhow::ensure!(data.len() >= 84, "STL too short for binary header");
        let num_tris = u32::from_le_bytes(data[80..84].try_into().unwrap()) as usize;
        anyhow::ensure!(data.len() >= 84 + num_tris * 50, "STL truncated");

        let mut triangles = Vec::with_capacity(num_tris);
        let mut bounds = AABB::empty();

        for i in 0..num_tris {
            let base = 84 + i * 50;
            // Skip normal (12 bytes), read 3 vertices (36 bytes)
            let mut verts = [Vec3::zero(); 3];
            for j in 0..3 {
                let off = base + 12 + j * 12;
                let x = f32::from_le_bytes(data[off..off + 4].try_into().unwrap()) as f64;
                let y = f32::from_le_bytes(data[off + 4..off + 8].try_into().unwrap()) as f64;
                let z = f32::from_le_bytes(data[off + 8..off + 12].try_into().unwrap()) as f64;
                verts[j] = Vec3::new(x, y, z);
                bounds.min = bounds.min.component_min(verts[j]);
                bounds.max = bounds.max.component_max(verts[j]);
            }
            triangles.push(Triangle { v: verts });
        }

        Ok(Self { triangles, bounds })
    }

    fn parse_ascii_stl(data: &[u8]) -> Result<Self> {
        let text = std::str::from_utf8(data).context("STL not valid UTF-8 for ASCII")?;
        let mut triangles = Vec::new();
        let mut bounds = AABB::empty();

        let mut verts: Vec<Vec3> = Vec::new();
        for line in text.lines() {
            let trimmed = line.trim();
            if let Some(rest) = trimmed.strip_prefix("vertex") {
                let parts: Vec<f64> = rest.split_whitespace()
                    .filter_map(|s| s.parse().ok())
                    .collect();
                if parts.len() == 3 {
                    let v = Vec3::new(parts[0], parts[1], parts[2]);
                    bounds.min = bounds.min.component_min(v);
                    bounds.max = bounds.max.component_max(v);
                    verts.push(v);
                    if verts.len() == 3 {
                        triangles.push(Triangle { v: [verts[0], verts[1], verts[2]] });
                        verts.clear();
                    }
                }
            }
        }

        Ok(Self { triangles, bounds })
    }

    pub fn from_obj(path: &Path) -> Result<Self> {
        let text = std::fs::read_to_string(path)
            .with_context(|| format!("reading {}", path.display()))?;

        let mut vertices: Vec<Vec3> = Vec::new();
        let mut triangles = Vec::new();
        let mut bounds = AABB::empty();

        for line in text.lines() {
            let trimmed = line.trim();
            if let Some(rest) = trimmed.strip_prefix("v ") {
                let parts: Vec<f64> = rest.split_whitespace()
                    .filter_map(|s| s.parse().ok())
                    .collect();
                if parts.len() >= 3 {
                    let v = Vec3::new(parts[0], parts[1], parts[2]);
                    bounds.min = bounds.min.component_min(v);
                    bounds.max = bounds.max.component_max(v);
                    vertices.push(v);
                }
            } else if let Some(rest) = trimmed.strip_prefix("f ") {
                let indices: Vec<usize> = rest.split_whitespace()
                    .filter_map(|s| {
                        let idx_str = s.split('/').next().unwrap_or(s);
                        idx_str.parse::<usize>().ok().map(|i| i - 1)
                    })
                    .collect();
                // Fan triangulation for polygons with > 3 vertices
                for i in 1..indices.len().saturating_sub(1) {
                    if let (Some(&a), Some(&b), Some(&c)) =
                        (indices.first(), indices.get(i), indices.get(i + 1))
                    {
                        if a < vertices.len() && b < vertices.len() && c < vertices.len() {
                            triangles.push(Triangle { v: [vertices[a], vertices[b], vertices[c]] });
                        }
                    }
                }
            }
        }

        Ok(Self { triangles, bounds })
    }

    pub fn from_3mf(path: &Path) -> Result<Self> {
        let file = std::fs::File::open(path)
            .with_context(|| format!("opening {}", path.display()))?;
        let reader = std::io::BufReader::new(file);
        let models = threemf::read(reader)
            .map_err(|e| anyhow::anyhow!("3MF parse error: {e}"))?;

        let mut triangles = Vec::new();
        let mut bounds = AABB::empty();

        for model in &models {
            for obj in &model.resources.object {
                let mesh = match &obj.mesh {
                    Some(m) => m,
                    None => continue,
                };
                let verts = &mesh.vertices.vertex;
                for tri in &mesh.triangles.triangle {
                    let v0 = &verts[tri.v1];
                    let v1 = &verts[tri.v2];
                    let v2 = &verts[tri.v3];
                    let a = Vec3::new(v0.x, v0.y, v0.z);
                    let b = Vec3::new(v1.x, v1.y, v1.z);
                    let c = Vec3::new(v2.x, v2.y, v2.z);
                    bounds.min = bounds.min.component_min(a).component_min(b).component_min(c);
                    bounds.max = bounds.max.component_max(a).component_max(b).component_max(c);
                    triangles.push(Triangle { v: [a, b, c] });
                }
            }
        }

        anyhow::ensure!(!triangles.is_empty(), "3MF contains no triangles");
        Ok(Self { triangles, bounds })
    }

    pub fn load(path: &Path) -> Result<Self> {
        let ext = path.extension()
            .and_then(|e| e.to_str())
            .unwrap_or("");
        let mut mesh = match ext.to_ascii_lowercase().as_str() {
            "stl" => Self::from_stl(path),
            "obj" => Self::from_obj(path),
            "3mf" => Self::from_3mf(path),
            _ if ext.is_empty() => anyhow::bail!("no file extension"),
            _ => anyhow::bail!("unsupported mesh format: .{ext}"),
        }?;
        mesh.sanitize();
        anyhow::ensure!(!mesh.triangles.is_empty(), "mesh contains no valid triangles after sanitization");
        Ok(mesh)
    }
}

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

    #[test]
    fn sanitize_removes_degenerate_triangles() {
        let good = Triangle {
            v: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        // Zero-area triangle: all three vertices collinear
        let degenerate = Triangle {
            v: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(2.0, 0.0, 0.0)],
        };
        // Collapsed edge: two vertices coincident
        let collapsed = Triangle {
            v: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        let mut mesh = TriangleMesh {
            triangles: vec![good, degenerate, collapsed],
            bounds: AABB {
                min: Vec3::new(0.0, 0.0, 0.0),
                max: Vec3::new(2.0, 1.0, 0.0),
            },
        };
        mesh.sanitize();
        assert_eq!(mesh.triangles.len(), 1, "only the good triangle should survive");
    }

    #[test]
    fn sanitize_removes_nan_vertices() {
        let good = Triangle {
            v: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        let bad = Triangle {
            v: [Vec3::new(f64::NAN, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        let mut mesh = TriangleMesh {
            triangles: vec![good, bad],
            bounds: AABB {
                min: Vec3::new(0.0, 0.0, 0.0),
                max: Vec3::new(1.0, 1.0, 0.0),
            },
        };
        mesh.sanitize();
        assert_eq!(mesh.triangles.len(), 1);
    }

    #[test]
    fn sanitize_keeps_valid_mesh() {
        let t1 = Triangle {
            v: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        let t2 = Triangle {
            v: [Vec3::new(1.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        let mut mesh = TriangleMesh {
            triangles: vec![t1, t2],
            bounds: AABB {
                min: Vec3::new(0.0, 0.0, 0.0),
                max: Vec3::new(1.0, 1.0, 0.0),
            },
        };
        mesh.sanitize();
        assert_eq!(mesh.triangles.len(), 2, "both valid triangles should survive");
    }

    #[test]
    fn sanitize_recomputes_bounds() {
        let good = Triangle {
            v: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
        };
        // This triangle has vertices way out at x=100 but is degenerate
        let degenerate = Triangle {
            v: [Vec3::new(100.0, 0.0, 0.0), Vec3::new(100.0, 0.0, 0.0), Vec3::new(100.0, 0.0, 0.0)],
        };
        let mut mesh = TriangleMesh {
            triangles: vec![good, degenerate],
            bounds: AABB {
                min: Vec3::new(0.0, 0.0, 0.0),
                max: Vec3::new(100.0, 1.0, 0.0),
            },
        };
        mesh.sanitize();
        assert!(mesh.bounds.max.x < 2.0, "bounds should not include removed triangle");
    }
}
