// use crate::Arena;
use crate::utils::sampling::PiecewiseConstant2D;
use anyhow::{Context, Result as AnyResult, bail};
use ply_rs::parser::Parser;
use ply_rs::ply::{DefaultElement, Property};
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike};
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
use shared::utils::{Ptr, Transform};
use std::fs::File;
use std::ops::Deref;
use std::path::Path;

/// Intermediate mesh from PLY
#[derive(Debug, Clone, Default)]
pub struct TriQuadMesh {
    pub p: Vec<Point3f>,
    pub n: Vec<Normal3f>,
    pub uv: Vec<Point2f>,
    pub face_indices: Vec<i32>,
    pub tri_indices: Vec<i32>,
    pub quad_indices: Vec<i32>,
}

#[derive(Debug)]
pub(crate) struct TriangleMeshStorage {
    pub p: Vec<Point3f>,
    pub n: Vec<Normal3f>,
    pub s: Vec<Vector3f>,
    pub uv: Vec<Point2f>,
    pub vertex_indices: Vec<i32>,
    pub face_indices: Vec<i32>,
}

#[derive(Debug)]
pub(crate) struct BilinearMeshStorage {
    pub vertex_indices: Vec<i32>,
    pub p: Vec<Point3f>,
    pub n: Vec<Normal3f>,
    pub uv: Vec<Point2f>,
    pub image_distribution: Option<PiecewiseConstant2D>,
}

#[derive(Debug)]
pub struct TriangleMesh {
    pub storage: Box<TriangleMeshStorage>,
    pub device: DeviceTriangleMesh,
}

#[derive(Debug)]
pub struct BilinearPatchMesh {
    pub storage: Box<BilinearMeshStorage>,
    pub device: DeviceBilinearPatchMesh,
}

impl Deref for TriangleMesh {
    type Target = DeviceTriangleMesh;
    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.device
    }
}

impl Deref for BilinearPatchMesh {
    type Target = DeviceBilinearPatchMesh;
    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.device
    }
}

impl TriangleMesh {
    pub fn new(
        render_from_object: &Transform,
        reverse_orientation: bool,
        vertex_indices: Vec<i32>,
        mut p: Vec<Point3f>,
        mut n: Vec<Normal3f>,
        mut s: Vec<Vector3f>,
        uv: Vec<Point2f>,
        face_indices: Vec<i32>,
    ) -> Self {
        let n_triangles = vertex_indices.len() / 3;
        let n_vertices = p.len();

        // Transform positions to render space
        for pt in p.iter_mut() {
            *pt = render_from_object.apply_to_point(*pt);
        }

        // Transform and optionally flip normals
        if !n.is_empty() {
            assert_eq!(n_vertices, n.len(), "Normal count mismatch");
            for nn in n.iter_mut() {
                *nn = render_from_object.apply_to_normal(*nn);
                if reverse_orientation {
                    *nn = -*nn;
                }
            }
        }

        // Transform tangents
        if !s.is_empty() {
            assert_eq!(n_vertices, s.len(), "Tangent count mismatch");
            for ss in s.iter_mut() {
                *ss = render_from_object.apply_to_vector(*ss);
            }
        }

        // Validate UVs
        if !uv.is_empty() {
            assert_eq!(n_vertices, uv.len(), "UV count mismatch");
        }

        // Validate face indices
        if !face_indices.is_empty() {
            assert_eq!(n_triangles, face_indices.len(), "Face index count mismatch");
        }

        let transform_swaps_handedness = render_from_object.swaps_handedness();

        let storage = Box::new(TriangleMeshStorage {
            vertex_indices,
            p,
            n,
            s,
            uv,
            face_indices,
        });

        // Build device struct with pointers into storage
        let device = DeviceTriangleMesh {
            n_triangles: n_triangles as u32,
            n_vertices: n_vertices as u32,
            vertex_indices: storage.vertex_indices.as_ptr().into(),
            p: storage.p.as_ptr().into(),
            n: if storage.n.is_empty() {
                Ptr::null()
            } else {
                storage.n.as_ptr().into()
            },
            s: if storage.s.is_empty() {
                Ptr::null()
            } else {
                storage.s.as_ptr().into()
            },
            uv: if storage.uv.is_empty() {
                Ptr::null()
            } else {
                storage.uv.as_ptr().into()
            },
            face_indices: if storage.face_indices.is_empty() {
                Ptr::null()
            } else {
                storage.face_indices.as_ptr().into()
            },
            reverse_orientation,
            transform_swaps_handedness,
        };

        Self { storage, device }
    }

    pub fn positions(&self) -> &[Point3f] {
        &self.storage.p
    }

    pub fn indices(&self) -> &[i32] {
        &self.storage.vertex_indices
    }

    pub fn normals(&self) -> &[Normal3f] {
        &self.storage.n
    }

    pub fn uvs(&self) -> &[Point2f] {
        &self.storage.uv
    }
}

impl BilinearPatchMesh {
    pub fn new(
        render_from_object: &Transform,
        reverse_orientation: bool,
        vertex_indices: Vec<i32>,
        mut p: Vec<Point3f>,
        mut n: Vec<Normal3f>,
        uv: Vec<Point2f>,
        image_distribution: Option<PiecewiseConstant2D>,
    ) -> Self {
        let n_patches = vertex_indices.len() / 4;
        let n_vertices = p.len();

        for pt in p.iter_mut() {
            *pt = render_from_object.apply_to_point(*pt);
        }

        if !n.is_empty() {
            assert_eq!(n_vertices, n.len(), "Normal count mismatch");
            for nn in n.iter_mut() {
                *nn = render_from_object.apply_to_normal(*nn);
                if reverse_orientation {
                    *nn = -*nn;
                }
            }
        }

        if !uv.is_empty() {
            assert_eq!(n_vertices, uv.len(), "UV count mismatch");
        }

        let transform_swaps_handedness = render_from_object.swaps_handedness();

        let storage = Box::new(BilinearMeshStorage {
            vertex_indices,
            p,
            n,
            uv,
            image_distribution,
        });

        let device = DeviceBilinearPatchMesh {
            n_patches: n_patches as u32,
            n_vertices: n_vertices as u32,
            vertex_indices: storage.vertex_indices.as_ptr().into(),
            p: storage.p.as_ptr().into(),
            n: if storage.n.is_empty() {
                Ptr::null()
            } else {
                storage.n.as_ptr().into()
            },
            uv: if storage.uv.is_empty() {
                Ptr::null()
            } else {
                storage.uv.as_ptr().into()
            },
            reverse_orientation,
            transform_swaps_handedness,
            image_distribution: storage
                .image_distribution
                .as_ref()
                .map(|d| Ptr::from(&d.device))
                .unwrap_or(Ptr::null()),
        };

        Self { storage, device }
    }
}

// PLY Helper Functions

fn get_float(elem: &DefaultElement, key: &str) -> AnyResult<f32> {
    match elem.get(key) {
        Some(Property::Float(v)) => Ok(*v),
        Some(Property::Double(v)) => Ok(*v as f32),
        Some(_) => bail!("Property {} is not a float", key),
        None => bail!("Property {} not found", key),
    }
}

fn get_int(elem: &DefaultElement, key: &str) -> AnyResult<i32> {
    match elem.get(key) {
        Some(Property::Int(v)) => Ok(*v),
        Some(Property::UInt(v)) => Ok(*v as i32),
        Some(Property::Short(v)) => Ok(*v as i32),
        Some(Property::UShort(v)) => Ok(*v as i32),
        Some(Property::Char(v)) => Ok(*v as i32),
        Some(Property::UChar(v)) => Ok(*v as i32),
        _ => bail!("Property {} not found or not integer", key),
    }
}

fn get_float_any(elem: &DefaultElement, keys: &[&str]) -> Option<f32> {
    for k in keys {
        if let Ok(val) = get_float(elem, k) {
            return Some(val);
        }
    }
    None
}

fn get_list_uint(elem: &DefaultElement, key: &str) -> AnyResult<Vec<u32>> {
    match elem.get(key) {
        Some(Property::ListInt(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
        Some(Property::ListUInt(vec)) => Ok(vec.clone()),
        Some(Property::ListUChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
        Some(Property::ListChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
        _ => bail!("Property {} is not a list", key),
    }
}

// TriQuadMesh Implementation

impl TriQuadMesh {
    pub fn read_ply<P: AsRef<Path>>(filename: P) -> AnyResult<Self> {
        let path = filename.as_ref();
        let filename_display = path.display().to_string();

        let mut f = File::open(path)
            .with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?;

        let p = Parser::<DefaultElement>::new();
        let ply = p
            .read_ply(&mut f)
            .with_context(|| format!("Unable to read/parse PLY file \"{}\"", filename_display))?;

        let mut mesh = TriQuadMesh::default();

        // Parse vertices
        if let Some(vertices) = ply.payload.get("vertex") {
            if vertices.is_empty() {
                bail!("{}: PLY file has no vertices", filename_display);
            }

            let first = &vertices[0];
            let has_uv = (first.contains_key("u") && first.contains_key("v"))
                || (first.contains_key("s") && first.contains_key("t"))
                || (first.contains_key("texture_u") && first.contains_key("texture_v"))
                || (first.contains_key("texture_s") && first.contains_key("texture_t"));
            let has_normal =
                first.contains_key("nx") && first.contains_key("ny") && first.contains_key("nz");

            mesh.p.reserve(vertices.len());
            if has_normal {
                mesh.n.reserve(vertices.len());
            }
            if has_uv {
                mesh.uv.reserve(vertices.len());
            }

            for v_elem in vertices {
                let x = get_float(v_elem, "x")?;
                let y = get_float(v_elem, "y")?;
                let z = get_float(v_elem, "z")?;
                mesh.p.push(Point3f::new(x, y, z));

                if has_normal {
                    let nx = get_float(v_elem, "nx").unwrap_or(0.0);
                    let ny = get_float(v_elem, "ny").unwrap_or(0.0);
                    let nz = get_float(v_elem, "nz").unwrap_or(0.0);
                    mesh.n.push(Normal3f::new(nx, ny, nz));
                }

                if has_uv {
                    let u =
                        get_float_any(v_elem, &["u", "s", "texture_u", "texture_s"]).unwrap_or(0.0);
                    let v =
                        get_float_any(v_elem, &["v", "t", "texture_v", "texture_t"]).unwrap_or(0.0);
                    mesh.uv.push(Point2f::new(u, v));
                }
            }
        } else {
            bail!("{}: PLY file has no vertex elements", filename_display);
        }

        // Parse faces
        if let Some(faces) = ply.payload.get("face") {
            mesh.tri_indices.reserve(faces.len() * 3);
            mesh.quad_indices.reserve(faces.len() * 4);

            for f_elem in faces {
                if let Ok(fi) = get_int(f_elem, "face_indices") {
                    mesh.face_indices.push(fi);
                }

                if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") {
                    match indices.len() {
                        3 => mesh.tri_indices.extend(indices.iter().map(|&i| i as i32)),
                        4 => mesh.quad_indices.extend(indices.iter().map(|&i| i as i32)),
                        n => {
                            log::warn!(
                                "{}: Skipping face with {} vertices (only 3 or 4 supported)",
                                filename_display,
                                n
                            );
                        }
                    }
                } else {
                    bail!("{}: vertex_indices not found in face", filename_display);
                }
            }
        } else {
            bail!("{}: PLY file has no face elements", filename_display);
        }

        // Validate indices
        let vertex_count = mesh.p.len() as i32;
        for &idx in &mesh.tri_indices {
            if idx >= vertex_count {
                bail!(
                    "{}: Vertex index {} out of bounds [0..{})",
                    filename_display,
                    idx,
                    vertex_count
                );
            }
        }
        for &idx in &mesh.quad_indices {
            if idx >= vertex_count {
                bail!(
                    "{}: Vertex index {} out of bounds [0..{})",
                    filename_display,
                    idx,
                    vertex_count
                );
            }
        }

        Ok(mesh)
    }

    pub fn convert_to_only_triangles(&mut self) {
        if self.quad_indices.is_empty() {
            return;
        }

        // Each quad becomes 2 triangles
        self.tri_indices.reserve(self.quad_indices.len() / 4 * 6);

        for quad in self.quad_indices.chunks_exact(4) {
            let (v0, v1, v2, v3) = (quad[0], quad[1], quad[2], quad[3]);

            // Triangle 1: v0, v1, v2
            self.tri_indices.push(v0);
            self.tri_indices.push(v1);
            self.tri_indices.push(v2);

            // Triangle 2: v0, v2, v3
            self.tri_indices.push(v0);
            self.tri_indices.push(v2);
            self.tri_indices.push(v3);
        }

        self.quad_indices.clear();
    }

    pub fn compute_normals(&mut self) {
        // Initialize normals to zero
        self.n = vec![Normal3f::new(0.0, 0.0, 0.0); self.p.len()];

        // Accumulate face normals for triangles
        for tri in self.tri_indices.chunks_exact(3) {
            let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);

            let p0 = self.p[i0];
            let p1 = self.p[i1];
            let p2 = self.p[i2];

            let v10 = p1 - p0;
            let v20 = p2 - p0;
            let face_normal = v10.cross(v20);

            // Accumulate (will normalize later)
            self.n[i0] = self.n[i0] + Normal3f::from(face_normal);
            self.n[i1] = self.n[i1] + Normal3f::from(face_normal);
            self.n[i2] = self.n[i2] + Normal3f::from(face_normal);
        }

        // Accumulate face normals for quads
        for quad in self.quad_indices.chunks_exact(4) {
            let indices: Vec<usize> = quad.iter().map(|&i| i as usize).collect();

            let p0 = self.p[indices[0]];
            let p1 = self.p[indices[1]];
            let p2 = self.p[indices[2]];

            let v10 = p1 - p0;
            let v20 = p2 - p0;
            let face_normal = v10.cross(v20);

            for &idx in &indices {
                self.n[idx] = self.n[idx] + Normal3f::from(face_normal);
            }
        }

        // Normalize all normals
        for normal in &mut self.n {
            let len_sq = normal.norm_squared();
            if len_sq > 0.0 {
                *normal = *normal / len_sq.sqrt();
            }
        }
    }

    /// Convert to a TriangleMesh, consuming self
    pub fn into_triangle_mesh(
        mut self,
        render_from_object: &Transform,
        reverse_orientation: bool,
    ) -> TriangleMesh {
        self.convert_to_only_triangles();

        if self.n.is_empty() {
            self.compute_normals();
        }

        TriangleMesh::new(
            render_from_object,
            reverse_orientation,
            self.tri_indices,
            self.p,
            self.n,
            Vec::new(), // s (tangents)
            self.uv,
            self.face_indices,
        )
    }
}

// Create from PLY directly

impl TriangleMesh {
    pub fn from_ply<P: AsRef<Path>>(
        filename: P,
        render_from_object: &Transform,
        reverse_orientation: bool,
    ) -> AnyResult<Self> {
        let mesh = TriQuadMesh::read_ply(filename)?;
        Ok(mesh.into_triangle_mesh(render_from_object, reverse_orientation))
    }
}
