//! Mesh data: CPU-side [`Mesh`] geometry, its GPU upload ([`GpuMesh`]), and
//! built-in primitive builders.
//!
//! A [`Vertex`] carries position, normal, and UV — the minimal set the Stage 4
//! forward renderer needs for lit, textured-ready geometry. Meshes are built on
//! the CPU (procedurally or, later, from a GLTF import) and uploaded once into a
//! [`GpuMesh`] for drawing.

use bytemuck::{Pod, Zeroable};
use wgpu::util::DeviceExt;

use crate::math::{Aabb, Vec2, Vec3};

/// A single mesh vertex: position, normal, and texture coordinate.
///
/// `repr(C)` + [`Pod`] so a `&[Vertex]` can be uploaded straight into a GPU
/// vertex buffer with no per-field marshalling.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
pub struct Vertex {
    /// Object-space position.
    pub position: [f32; 3],
    /// Object-space normal (expected unit length for correct lighting).
    pub normal: [f32; 3],
    /// Texture coordinate.
    pub uv: [f32; 2],
}

impl Vertex {
    /// Builds a vertex from math types.
    pub fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
        Self {
            position: position.to_array(),
            normal: normal.to_array(),
            uv: uv.to_array(),
        }
    }

    /// The `wgpu` vertex buffer layout matching this struct's fields
    /// (`@location(0)` position, `@location(1)` normal, `@location(2)` uv).
    pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
        array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
        step_mode: wgpu::VertexStepMode::Vertex,
        attributes: &wgpu::vertex_attr_array![
            0 => Float32x3, // position
            1 => Float32x3, // normal
            2 => Float32x2, // uv
        ],
    };
}

/// CPU-side mesh geometry: an indexed triangle list.
///
/// Indices are `u32` (32-bit), so meshes are not limited to 65k vertices.
#[derive(Debug, Clone, Default)]
pub struct Mesh {
    /// Vertex data.
    pub vertices: Vec<Vertex>,
    /// Triangle indices into [`vertices`](Self::vertices), three per triangle.
    pub indices: Vec<u32>,
}

impl Mesh {
    /// Creates a mesh from raw vertex and index data.
    pub fn new(vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
        Self { vertices, indices }
    }

    /// Number of triangles (index count / 3).
    pub fn triangle_count(&self) -> usize {
        self.indices.len() / 3
    }

    /// The axis-aligned bounds of the mesh in object space
    /// ([`Aabb::EMPTY`](crate::math::Aabb) for an empty mesh).
    pub fn bounds(&self) -> Aabb {
        Aabb::from_points(self.vertices.iter().map(|v| Vec3::from_array(v.position)))
    }

    /// Uploads the mesh into GPU vertex/index buffers for drawing.
    pub fn upload(&self, device: &wgpu::Device, label: &str) -> GpuMesh {
        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some(&format!("{label}.vertices")),
            contents: bytemuck::cast_slice(&self.vertices),
            usage: wgpu::BufferUsages::VERTEX,
        });
        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some(&format!("{label}.indices")),
            contents: bytemuck::cast_slice(&self.indices),
            usage: wgpu::BufferUsages::INDEX,
        });
        GpuMesh {
            vertex_buffer,
            index_buffer,
            index_count: self.indices.len() as u32,
        }
    }

    /// A unit cube centered at the origin (side length 1), with per-face normals
    /// and UVs (so each face is flat-shaded correctly).
    pub fn cube() -> Self {
        Self::box_mesh(Vec3::splat(1.0))
    }

    /// An axis-aligned box of the given `size` (full extents), centered at the
    /// origin, with per-face normals and UVs.
    pub fn box_mesh(size: Vec3) -> Self {
        let h = size * 0.5;
        // (normal, then the four corners CCW seen from outside)
        let faces: [(Vec3, [Vec3; 4]); 6] = [
            // +X
            (
                Vec3::X,
                [
                    Vec3::new(h.x, -h.y, h.z),
                    Vec3::new(h.x, -h.y, -h.z),
                    Vec3::new(h.x, h.y, -h.z),
                    Vec3::new(h.x, h.y, h.z),
                ],
            ),
            // -X
            (
                Vec3::NEG_X,
                [
                    Vec3::new(-h.x, -h.y, -h.z),
                    Vec3::new(-h.x, -h.y, h.z),
                    Vec3::new(-h.x, h.y, h.z),
                    Vec3::new(-h.x, h.y, -h.z),
                ],
            ),
            // +Y
            (
                Vec3::Y,
                [
                    Vec3::new(-h.x, h.y, h.z),
                    Vec3::new(h.x, h.y, h.z),
                    Vec3::new(h.x, h.y, -h.z),
                    Vec3::new(-h.x, h.y, -h.z),
                ],
            ),
            // -Y
            (
                Vec3::NEG_Y,
                [
                    Vec3::new(-h.x, -h.y, -h.z),
                    Vec3::new(h.x, -h.y, -h.z),
                    Vec3::new(h.x, -h.y, h.z),
                    Vec3::new(-h.x, -h.y, h.z),
                ],
            ),
            // +Z
            (
                Vec3::Z,
                [
                    Vec3::new(-h.x, -h.y, h.z),
                    Vec3::new(h.x, -h.y, h.z),
                    Vec3::new(h.x, h.y, h.z),
                    Vec3::new(-h.x, h.y, h.z),
                ],
            ),
            // -Z
            (
                Vec3::NEG_Z,
                [
                    Vec3::new(h.x, -h.y, -h.z),
                    Vec3::new(-h.x, -h.y, -h.z),
                    Vec3::new(-h.x, h.y, -h.z),
                    Vec3::new(h.x, h.y, -h.z),
                ],
            ),
        ];
        let uvs = [
            Vec2::new(0.0, 1.0),
            Vec2::new(1.0, 1.0),
            Vec2::new(1.0, 0.0),
            Vec2::new(0.0, 0.0),
        ];
        let mut vertices = Vec::with_capacity(24);
        let mut indices = Vec::with_capacity(36);
        for (normal, corners) in faces {
            let base = vertices.len() as u32;
            for (corner, uv) in corners.iter().zip(uvs.iter()) {
                vertices.push(Vertex::new(*corner, normal, *uv));
            }
            indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
        }
        Self::new(vertices, indices)
    }

    /// A flat plane of `size` units on the XZ axes, centered at the origin,
    /// facing `+Y`. Useful as a ground reference.
    pub fn plane(size: f32) -> Self {
        let h = size * 0.5;
        let n = Vec3::Y;
        let vertices = vec![
            Vertex::new(Vec3::new(-h, 0.0, h), n, Vec2::new(0.0, 1.0)),
            Vertex::new(Vec3::new(h, 0.0, h), n, Vec2::new(1.0, 1.0)),
            Vertex::new(Vec3::new(h, 0.0, -h), n, Vec2::new(1.0, 0.0)),
            Vertex::new(Vec3::new(-h, 0.0, -h), n, Vec2::new(0.0, 0.0)),
        ];
        Self::new(vertices, vec![0, 1, 2, 0, 2, 3])
    }

    /// A UV sphere of `radius` with `sectors` longitudinal and `stacks`
    /// latitudinal divisions. Normals are the (normalized) positions.
    pub fn uv_sphere(radius: f32, sectors: u32, stacks: u32) -> Self {
        use std::f32::consts::PI;
        let sectors = sectors.max(3);
        let stacks = stacks.max(2);
        let mut vertices = Vec::new();
        for i in 0..=stacks {
            // From +Y pole (phi=0) to -Y pole (phi=PI).
            let phi = PI * i as f32 / stacks as f32;
            let (sin_phi, cos_phi) = phi.sin_cos();
            for j in 0..=sectors {
                let theta = 2.0 * PI * j as f32 / sectors as f32;
                let (sin_theta, cos_theta) = theta.sin_cos();
                let dir = Vec3::new(sin_phi * cos_theta, cos_phi, sin_phi * sin_theta);
                let uv = Vec2::new(j as f32 / sectors as f32, i as f32 / stacks as f32);
                vertices.push(Vertex::new(dir * radius, dir, uv));
            }
        }
        let mut indices = Vec::new();
        let row = sectors + 1;
        for i in 0..stacks {
            for j in 0..sectors {
                let a = i * row + j;
                let b = a + row;
                // Two triangles per quad; skip degenerate ones at the poles.
                // Vertex order is `a → a+1 → b` and `a+1 → b+1 → b`, which
                // winds the quad CCW when seen from *outside* the sphere —
                // the wgpu front-face convention. The previous ordering
                // (`a, b, a+1` / `a+1, b, b+1`) wound them CW from outside,
                // which made back-face culling eat the sphere's surface and
                // showed intersecting opaque meshes through it.
                if i != 0 {
                    indices.extend_from_slice(&[a, a + 1, b]);
                }
                if i != stacks - 1 {
                    indices.extend_from_slice(&[a + 1, b + 1, b]);
                }
            }
        }
        Self::new(vertices, indices)
    }
}

/// A mesh uploaded to the GPU: vertex and index buffers ready to draw.
pub struct GpuMesh {
    /// Vertex buffer, laid out per [`Vertex::LAYOUT`].
    pub vertex_buffer: wgpu::Buffer,
    /// `u32` index buffer.
    pub index_buffer: wgpu::Buffer,
    /// Number of indices to draw.
    pub index_count: u32,
}
