use std::collections::HashMap;
use std::hash::{Hash, Hasher};

use serde::{Deserialize, Serialize};

use bone_types::{
    AngleTolerance, BrepFaceId, ChordHeightTolerance, FaceLabel, MeshGeneration, Point3, Tolerance,
    UnitVec3,
};
use slotmap::Key;
use truck_meshalgo::prelude::PolygonMesh;
use truck_meshalgo::tessellation::RobustMeshableShape;
use truck_modeling::{Invertible, TOLERANCE};

use super::BrepSolid;
use super::convert::{point_from_truck, try_unit_from_truck};

const UNIT_NORMAL_TOLERANCE: Tolerance = Tolerance::new(1.0e-9);

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FaceMesh {
    face: BrepFaceId,
    label: FaceLabel,
    positions: Vec<Point3>,
    normals: Vec<UnitVec3>,
    triangles: Vec<[u32; 3]>,
}

impl FaceMesh {
    #[must_use]
    pub fn face(&self) -> BrepFaceId {
        self.face
    }

    #[must_use]
    pub fn label(&self) -> FaceLabel {
        self.label
    }

    #[must_use]
    pub fn positions(&self) -> &[Point3] {
        &self.positions
    }

    #[must_use]
    pub fn normals(&self) -> &[UnitVec3] {
        &self.normals
    }

    #[must_use]
    pub fn triangles(&self) -> &[[u32; 3]] {
        &self.triangles
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SolidMesh {
    faces: Vec<FaceMesh>,
    generation: MeshGeneration,
}

impl SolidMesh {
    #[must_use]
    pub fn faces(&self) -> &[FaceMesh] {
        &self.faces
    }

    #[must_use]
    pub fn generation(&self) -> MeshGeneration {
        self.generation
    }

    pub fn validate(&self, tolerance: Tolerance) -> Result<(), MeshError> {
        self.faces
            .iter()
            .try_for_each(|slab| validate_slab(slab, tolerance))
    }
}

#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum MeshError {
    #[error("face {0:?} produced no triangles")]
    EmptyFace(BrepFaceId),
    #[error("face {face:?} triangle {triangle} has an edge below tolerance")]
    DegenerateTriangle { face: BrepFaceId, triangle: usize },
    #[error("face {face:?} triangle {triangle} winds against its surface normal")]
    NonCcwTriangle { face: BrepFaceId, triangle: usize },
    #[error("face {face:?} has an edge incident to more than two triangles")]
    NonManifoldEdge { face: BrepFaceId },
    #[error("face {face:?} surface tessellation produced no polygon")]
    SurfaceTessellationFailed { face: BrepFaceId },
    #[error("face {face:?} surface emitted a normal that is not unit length")]
    InvalidSurfaceNormal { face: BrepFaceId },
    #[error("face {face:?} slab exceeds u32 vertex addressing")]
    SlabTooLarge { face: BrepFaceId },
}

pub(super) fn tessellate_solid(
    brep: &BrepSolid,
    chord: ChordHeightTolerance,
    angle: AngleTolerance,
) -> Result<SolidMesh, MeshError> {
    let tol = effective_tolerance(brep, chord, angle);
    let original = brep.arena.solid();
    let meshed = original.robust_triangulation(tol);

    let face_index = brep.arena.face_index();
    let mut slabs: HashMap<BrepFaceId, FaceMesh> = brep
        .iter_faces()
        .map(|face| {
            (
                face.id(),
                FaceMesh {
                    face: face.id(),
                    label: face.label(),
                    positions: Vec::new(),
                    normals: Vec::new(),
                    triangles: Vec::new(),
                },
            )
        })
        .collect();

    original
        .boundaries()
        .iter()
        .zip(meshed.boundaries().iter())
        .flat_map(|(orig_shell, mesh_shell)| orig_shell.face_iter().zip(mesh_shell.face_iter()))
        .try_for_each(|(orig_face, mesh_face)| {
            let brep_face = *face_index
                .get(&orig_face.id())
                .unwrap_or_else(|| unreachable!("face_index covers every truck face in the arena"));
            let Some(mut polygon) = mesh_face.surface() else {
                return Err(MeshError::SurfaceTessellationFailed { face: brep_face });
            };
            if !mesh_face.orientation() {
                polygon.invert();
            }
            let slab = slabs.get_mut(&brep_face).unwrap_or_else(|| {
                unreachable!("slabs pre-filled from iter_faces over the same slotmap")
            });
            append_polygon(slab, &polygon)
        })?;

    let faces: Vec<FaceMesh> = brep
        .iter_faces()
        .map(|face| {
            slabs
                .remove(&face.id())
                .unwrap_or_else(|| unreachable!("slabs pre-filled from iter_faces"))
        })
        .collect();
    faces.iter().try_for_each(|slab| {
        if slab.triangles.is_empty() {
            Err(MeshError::EmptyFace(slab.face))
        } else {
            Ok(())
        }
    })?;
    let generation = derive_generation(&faces, chord, angle);
    Ok(SolidMesh { faces, generation })
}

fn effective_tolerance(
    brep: &BrepSolid,
    chord: ChordHeightTolerance,
    angle: AngleTolerance,
) -> f64 {
    let chord_mm = chord.millimeters();
    let angle_chord = brep.bounding_box().map_or(f64::INFINITY, |bbox| {
        let radius = bbox.extent().norm_mm() / 2.0;
        radius * (1.0 - (angle.radians() / 2.0).cos())
    });
    chord_mm.min(angle_chord).max(TOLERANCE)
}

fn append_polygon(slab: &mut FaceMesh, polygon: &PolygonMesh) -> Result<(), MeshError> {
    let mut lookup: HashMap<(usize, Option<usize>), u32> = HashMap::new();
    let positions = polygon.positions();
    let normals = polygon.normals();
    polygon.tri_faces().iter().try_for_each(|tri| {
        let [v0, v1, v2] = *tri;
        let a = append_vertex(slab, positions, normals, v0, &mut lookup)?;
        let b = append_vertex(slab, positions, normals, v1, &mut lookup)?;
        let c = append_vertex(slab, positions, normals, v2, &mut lookup)?;
        slab.triangles.push([a, b, c]);
        Ok(())
    })
}

fn append_vertex(
    slab: &mut FaceMesh,
    positions: &[truck_modeling::Point3],
    normals: &[truck_modeling::Vector3],
    v: truck_meshalgo::prelude::StandardVertex,
    lookup: &mut HashMap<(usize, Option<usize>), u32>,
) -> Result<u32, MeshError> {
    if let Some(existing) = lookup.get(&(v.pos, v.nor)) {
        return Ok(*existing);
    }
    let Some(nor_index) = v.nor else {
        return Err(MeshError::InvalidSurfaceNormal { face: slab.face });
    };
    let Some(normal) = try_unit_from_truck(normals[nor_index], UNIT_NORMAL_TOLERANCE) else {
        return Err(MeshError::InvalidSurfaceNormal { face: slab.face });
    };
    let index = u32::try_from(slab.positions.len())
        .map_err(|_| MeshError::SlabTooLarge { face: slab.face })?;
    slab.positions.push(point_from_truck(positions[v.pos]));
    slab.normals.push(normal);
    lookup.insert((v.pos, v.nor), index);
    Ok(index)
}

struct StableHasher(blake3::Hasher);

impl Hasher for StableHasher {
    fn write(&mut self, bytes: &[u8]) {
        self.0.update(bytes);
    }

    fn write_u8(&mut self, i: u8) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_u16(&mut self, i: u16) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_u32(&mut self, i: u32) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_u64(&mut self, i: u64) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_u128(&mut self, i: u128) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_usize(&mut self, i: usize) {
        let Ok(widened) = u64::try_from(i) else {
            unreachable!("usize exceeds u64 only above 64-bit platforms");
        };
        self.0.update(&widened.to_le_bytes());
    }

    fn write_i8(&mut self, i: i8) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_i16(&mut self, i: i16) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_i32(&mut self, i: i32) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_i64(&mut self, i: i64) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_i128(&mut self, i: i128) {
        self.0.update(&i.to_le_bytes());
    }

    fn write_isize(&mut self, i: isize) {
        let Ok(widened) = i64::try_from(i) else {
            unreachable!("isize exceeds i64 only above 64-bit platforms");
        };
        self.0.update(&widened.to_le_bytes());
    }

    fn finish(&self) -> u64 {
        let digest = self.0.finalize();
        let Ok(head) = <[u8; 8]>::try_from(&digest.as_bytes()[..8]) else {
            unreachable!("blake3 digest is 32 bytes");
        };
        u64::from_le_bytes(head)
    }
}

fn derive_generation(
    faces: &[FaceMesh],
    chord: ChordHeightTolerance,
    angle: AngleTolerance,
) -> MeshGeneration {
    let mut hasher = StableHasher(blake3::Hasher::new());
    chord.millimeters().to_bits().hash(&mut hasher);
    angle.radians().to_bits().hash(&mut hasher);
    faces.iter().for_each(|slab| {
        slab.face.data().as_ffi().hash(&mut hasher);
        slab.label.hash(&mut hasher);
        slab.positions.iter().for_each(|p| {
            let (x, y, z) = p.coords_mm();
            x.to_bits().hash(&mut hasher);
            y.to_bits().hash(&mut hasher);
            z.to_bits().hash(&mut hasher);
        });
        slab.normals.iter().for_each(|n| {
            let (x, y, z) = n.components();
            x.to_bits().hash(&mut hasher);
            y.to_bits().hash(&mut hasher);
            z.to_bits().hash(&mut hasher);
        });
        slab.triangles.iter().for_each(|tri| tri.hash(&mut hasher));
    });
    MeshGeneration::new(hasher.finish())
}

fn validate_slab(slab: &FaceMesh, tolerance: Tolerance) -> Result<(), MeshError> {
    slab.triangles
        .iter()
        .enumerate()
        .try_for_each(|(triangle_index, indices)| {
            let p = corners(slab, *indices);
            check_non_degenerate(slab.face, triangle_index, p, tolerance)?;
            check_ccw_winding(slab, triangle_index, *indices, p)
        })?;
    check_manifold(slab)
}

type Corners = [Point3; 3];

fn corners(slab: &FaceMesh, indices: [u32; 3]) -> Corners {
    indices.map(|i| slab.positions[i as usize])
}

fn check_non_degenerate(
    face: BrepFaceId,
    triangle: usize,
    corners: Corners,
    tolerance: Tolerance,
) -> Result<(), MeshError> {
    let min_sq = tolerance.value() * tolerance.value();
    let edge_sq = |a: usize, b: usize| {
        let d = corners[b] - corners[a];
        d.norm_squared_mm2()
    };
    if edge_sq(0, 1) <= min_sq || edge_sq(1, 2) <= min_sq || edge_sq(2, 0) <= min_sq {
        Err(MeshError::DegenerateTriangle { face, triangle })
    } else {
        Ok(())
    }
}

fn check_ccw_winding(
    slab: &FaceMesh,
    triangle: usize,
    indices: [u32; 3],
    corners: Corners,
) -> Result<(), MeshError> {
    let edge_a = corners[1] - corners[0];
    let edge_b = corners[2] - corners[0];
    let cross = edge_a.cross(edge_b);
    let normal_sum = indices.iter().fold([0.0_f64; 3], |acc, idx| {
        let (nx, ny, nz) = slab.normals[*idx as usize].components();
        [acc[0] + nx, acc[1] + ny, acc[2] + nz]
    });
    let (cx, cy, cz) = cross.coords_mm();
    let dot = cx * normal_sum[0] + cy * normal_sum[1] + cz * normal_sum[2];
    if dot > 0.0 {
        Ok(())
    } else {
        Err(MeshError::NonCcwTriangle {
            face: slab.face,
            triangle,
        })
    }
}

fn check_manifold(slab: &FaceMesh) -> Result<(), MeshError> {
    let counts = slab
        .triangles
        .iter()
        .flat_map(|tri| {
            [
                edge_key(tri[0], tri[1]),
                edge_key(tri[1], tri[2]),
                edge_key(tri[2], tri[0]),
            ]
        })
        .fold(HashMap::<(u32, u32), u32>::new(), |mut counts, key| {
            *counts.entry(key).or_insert(0) += 1;
            counts
        });
    if counts.values().any(|count| *count > 2) {
        Err(MeshError::NonManifoldEdge { face: slab.face })
    } else {
        Ok(())
    }
}

fn edge_key(a: u32, b: u32) -> (u32, u32) {
    if a < b { (a, b) } else { (b, a) }
}

#[cfg(test)]
mod tests {
    use super::StableHasher;
    use std::hash::Hasher;

    fn digest_via(emit: impl FnOnce(&mut StableHasher)) -> u64 {
        let mut hasher = StableHasher(blake3::Hasher::new());
        emit(&mut hasher);
        hasher.finish()
    }

    #[test]
    fn integer_writes_are_little_endian_not_native() {
        let value = 0x0102_0304_0506_0708_u64;
        let method = digest_via(|h| h.write_u64(value));
        assert_eq!(
            method,
            digest_via(|h| h.write(&value.to_le_bytes())),
            "write_u64 must emit little-endian bytes on every host"
        );
        assert_ne!(
            method,
            digest_via(|h| h.write(&value.to_be_bytes())),
            "a big-endian framing must not collide with the canonical one"
        );
    }

    #[test]
    fn pointer_sized_writes_are_normalized_to_64_bit() {
        assert_eq!(
            digest_via(|h| h.write_usize(0x0102_0304)),
            digest_via(|h| h.write_u64(0x0102_0304_u64)),
            "usize must hash as a 64-bit value so 32-bit and 64-bit hosts agree"
        );
        assert_eq!(
            digest_via(|h| h.write_isize(-0x0102_0304)),
            digest_via(|h| h.write_i64(-0x0102_0304_i64)),
            "isize must hash as a 64-bit value so 32-bit and 64-bit hosts agree"
        );
    }
}
