//! Render scene data -- GPU-facing presentation state.
//! This is NOT canonical world truth. It is rebuilt each frame from
//! the client mirror via the presentation update phase.

use crate::handles::{MaterialHandle, MeshHandle};

/// GPU-facing scene data. SoA layout for cache-friendly iteration.
#[derive(Debug, Clone)]
pub struct RenderSceneData {
    /// World-space 4x4 column-major transform matrices.
    pub transforms: Vec<[f32; 16]>,
    /// Mesh handle per entity (None = no mesh).
    pub meshes: Vec<Option<MeshHandle>>,
    /// Material handle per entity (None = default material).
    pub materials: Vec<Option<MaterialHandle>>,
    /// Visibility flag per entity.
    pub visibility: Vec<bool>,
    /// Picking ID per entity (for GPU picking readback).
    pub picking_ids: Vec<u32>,
}

impl RenderSceneData {
    /// Create with pre-allocated capacity.
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            transforms: Vec::with_capacity(capacity),
            meshes: Vec::with_capacity(capacity),
            materials: Vec::with_capacity(capacity),
            visibility: Vec::with_capacity(capacity),
            picking_ids: Vec::with_capacity(capacity),
        }
    }

    /// Clear all data for frame rebuild.
    pub fn clear(&mut self) {
        self.transforms.clear();
        self.meshes.clear();
        self.materials.clear();
        self.visibility.clear();
        self.picking_ids.clear();
    }

    /// Number of entities in the scene.
    pub fn len(&self) -> usize {
        self.transforms.len()
    }

    /// Whether the scene is empty.
    pub fn is_empty(&self) -> bool {
        self.transforms.is_empty()
    }

    /// Push a single entity's data.
    pub fn push(
        &mut self,
        transform: [f32; 16],
        mesh: Option<MeshHandle>,
        material: Option<MaterialHandle>,
        visible: bool,
        picking_id: u32,
    ) {
        self.transforms.push(transform);
        self.meshes.push(mesh);
        self.materials.push(material);
        self.visibility.push(visible);
        self.picking_ids.push(picking_id);
    }

    /// Count of visible entities with meshes.
    pub fn visible_mesh_count(&self) -> usize {
        self.visibility
            .iter()
            .zip(self.meshes.iter())
            .filter(|(&vis, mesh)| vis && mesh.is_some())
            .count()
    }
}

/// Convenience alias for `RenderSceneData`.
pub type RenderScene = RenderSceneData;

impl Default for RenderSceneData {
    fn default() -> Self {
        Self::with_capacity(0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use dreamwell_engine::game_object::Transform;

    #[test]
    fn with_capacity_empty() {
        let s = RenderSceneData::with_capacity(100);
        assert!(s.is_empty());
        assert_eq!(s.len(), 0);
    }

    #[test]
    fn push_and_len() {
        let mut s = RenderSceneData::with_capacity(10);
        let t = Transform::default().to_matrix();
        s.push(t, Some(MeshHandle(0, 0)), None, true, 1);
        s.push(t, None, None, false, 2);
        assert_eq!(s.len(), 2);
    }

    #[test]
    fn clear_resets() {
        let mut s = RenderSceneData::with_capacity(10);
        let t = Transform::default().to_matrix();
        s.push(t, Some(MeshHandle(0, 0)), None, true, 1);
        s.clear();
        assert!(s.is_empty());
    }

    #[test]
    fn visible_mesh_count() {
        let mut s = RenderSceneData::with_capacity(10);
        let t = Transform::default().to_matrix();
        s.push(t, Some(MeshHandle(0, 0)), None, true, 1); // visible with mesh
        s.push(t, None, None, true, 2); // visible without mesh
        s.push(t, Some(MeshHandle(1, 0)), None, false, 3); // invisible with mesh
        assert_eq!(s.visible_mesh_count(), 1);
    }
}
