//! Mesh geometry from [`Primitive`] authoring data.

use bevy::prelude::*;

use crate::{
    authoring_component_active, AuthoringComponentStates, InspectorOrder, LevelObject, Primitive,
    PrimitiveShape, COMPONENT_PRIMITIVE,
};

pub(crate) fn primitive_mesh(primitive: &Primitive) -> Mesh {
    let mut mesh = match primitive.shape {
        PrimitiveShape::Box => Cuboid::new(primitive.size.x, primitive.size.y, primitive.size.z)
            .mesh()
            .build(),
        PrimitiveShape::Sphere => {
            let radius = primitive.size.max_element() * 0.5;
            Sphere::new(radius).mesh().uv(48, 24)
        }
        PrimitiveShape::Ramp => Cuboid::new(primitive.size.x, primitive.size.y, primitive.size.z)
            .mesh()
            .build(),
    };

    if let Err(error) = mesh.generate_tangents() {
        warn!("Primitive mesh is not Solari-compatible: failed to generate tangents: {error:?}");
    }

    mesh
}

#[allow(clippy::type_complexity)]
pub fn hydrate_primitives(
    mut commands: Commands,
    primitives: Query<
        (
            Entity,
            &Primitive,
            Option<&AuthoringComponentStates>,
            Option<&InspectorOrder>,
        ),
        (
            With<LevelObject>,
            Or<(
                Added<Primitive>,
                Changed<Primitive>,
                Changed<AuthoringComponentStates>,
                Changed<InspectorOrder>,
            )>,
        ),
    >,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    for (entity, primitive, states, legacy_order) in &primitives {
        if !authoring_component_active(states, legacy_order, COMPONENT_PRIMITIVE) {
            commands
                .entity(entity)
                .remove::<Mesh3d>()
                .remove::<MeshMaterial3d<StandardMaterial>>();
            continue;
        }
        let mesh = meshes.add(primitive_mesh(primitive));

        commands.entity(entity).insert(Mesh3d(mesh));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bevy::mesh::{Indices, PrimitiveTopology};

    #[test]
    fn primitive_meshes_are_solari_compatible() {
        for primitive in [
            Primitive::cuboid(Vec3::ONE),
            Primitive::sphere(1.0),
            Primitive::ramp(Vec3::ONE),
        ] {
            let mesh = primitive_mesh(&primitive);

            assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
            assert!(mesh.attribute(Mesh::ATTRIBUTE_POSITION).is_some());
            assert!(mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_some());
            assert!(mesh.attribute(Mesh::ATTRIBUTE_UV_0).is_some());
            assert!(mesh.attribute(Mesh::ATTRIBUTE_TANGENT).is_some());
            assert!(matches!(mesh.indices(), Some(Indices::U32(_))));
            assert!(mesh.enable_raytracing);
        }
    }
}
