//! Renderable scene components: [`MeshRenderer`] and [`PrimitiveShape`].
//!
//! A [`MeshRenderer`] is the component that makes a scene entity show up in the
//! 3D viewport: it pairs a mesh source with a [`Material`]. Stage 4 ships the
//! built-in [`PrimitiveShape`] source (cube/sphere/plane) — lightweight and
//! serializable, so the editor (and later scripts/AI agents) can author what an
//! entity renders. Imported meshes attach later via a mesh-asset handle.

use serde::{Deserialize, Serialize};

use super::{Material, Mesh};
use crate::math::{Aabb, Vec3};

/// A built-in primitive mesh an entity can render.
///
/// This names a shape rather than embedding vertex data, so it stays tiny,
/// serializable, and cheap to edit; the renderer resolves it to a (cached)
/// [`Mesh`]/GPU buffer.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    Default,
    Serialize,
    Deserialize,
    crate::reflect::ReflectEnum,
)]
pub enum PrimitiveShape {
    /// Unit cube centered at the origin.
    #[default]
    Cube,
    /// Unit-radius UV sphere.
    Sphere,
    /// A 1×1 ground plane on the XZ axes, facing `+Y`.
    Plane,
}

impl PrimitiveShape {
    /// All shapes, for building caches / editor menus.
    pub const ALL: [PrimitiveShape; 3] = [
        PrimitiveShape::Cube,
        PrimitiveShape::Sphere,
        PrimitiveShape::Plane,
    ];

    /// A human-readable label.
    pub fn label(self) -> &'static str {
        match self {
            PrimitiveShape::Cube => "Cube",
            PrimitiveShape::Sphere => "Sphere",
            PrimitiveShape::Plane => "Plane",
        }
    }

    /// Builds the CPU [`Mesh`] for this shape.
    pub fn mesh(self) -> Mesh {
        match self {
            PrimitiveShape::Cube => Mesh::cube(),
            PrimitiveShape::Sphere => Mesh::uv_sphere(1.0, 32, 16),
            PrimitiveShape::Plane => Mesh::plane(1.0),
        }
    }

    /// The object-space bounds of this shape, without building a mesh — used for
    /// ray-picking and culling.
    pub fn local_bounds(self) -> Aabb {
        let half = match self {
            PrimitiveShape::Cube => Vec3::splat(0.5),
            PrimitiveShape::Sphere => Vec3::ONE,
            PrimitiveShape::Plane => Vec3::new(0.5, 0.0, 0.5),
        };
        Aabb::from_center_half_extents(Vec3::ZERO, half)
    }
}

/// Component: what an entity renders.
///
/// Attach to a scene entity (via the ECS) to make it appear in a forward pass.
/// Stage 4 sources the mesh from a [`PrimitiveShape`]; the [`Material`] is
/// edited in the inspector. Both are serializable, supporting the engine's
/// dual-editable (editor + script/AI) component goal.
#[derive(
    Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, crate::reflect::Reflect,
)]
pub struct MeshRenderer {
    /// The mesh to draw.
    pub shape: PrimitiveShape,
    /// The surface material.
    pub material: Material,
}

impl MeshRenderer {
    /// A renderer for `shape` with the default material.
    pub fn new(shape: PrimitiveShape) -> Self {
        Self {
            shape,
            material: Material::default(),
        }
    }

    /// A renderer for `shape` with an explicit `material`.
    pub fn with_material(shape: PrimitiveShape, material: Material) -> Self {
        Self { shape, material }
    }
}

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

    #[test]
    fn every_shape_builds_a_nonempty_mesh() {
        for shape in PrimitiveShape::ALL {
            let mesh = shape.mesh();
            assert!(!mesh.vertices.is_empty(), "{shape:?} has no vertices");
            assert!(mesh.triangle_count() > 0, "{shape:?} has no triangles");
        }
    }

    #[test]
    fn mesh_renderer_round_trips_through_ron() {
        let mr = MeshRenderer::with_material(
            PrimitiveShape::Sphere,
            Material::metal(Color::rgb(0.2, 0.4, 0.8), 0.25),
        );
        let ron = ron::to_string(&mr).unwrap();
        let back: MeshRenderer = ron::from_str(&ron).unwrap();
        assert_eq!(mr, back);
    }
}
