//! Blender-Modell → Render-Geometrie.
//!
//! Nimmt das neutrale [`Model`] aus engine::gltf und baut daraus ein
//! [`Mesh`] (Vertices + nach Textur gruppierte Index-Batches); mehrere
//! Modelle verschmelzen über `Mesh::append` zu einem Buffer.
//!
//! Texturen: der Blender-Materialname *ist* der Texturname — aufgelöst zu
//! `assets/textures/{name}.tga` (macht der Aufrufer). Faces ohne Material
//! (`""`) laufen unter [`FALLBACK_TEXTURE`].
//!
//! UVs kommen unverändert durch (Modell und `tga::Image` teilen den
//! Oben-links-Ursprung). Nur `visible`-Objekte werden ausgegeben;
//! Collider-/Zonen-Volumen sind Sache von engine::collision und
//! engine::trigger.

use crate::engine::model::Model;
use crate::render::scene::{Batch, Mesh, Vertex};

/// Texturname für Faces ohne `usemtl` (Material `""`).
pub const FALLBACK_TEXTURE: &str = "placeholder";

/// Distinkte Texturnamen des Modells (sortiert), `""` bereits auf den
/// Fallback gemappt.
pub fn texture_names(model: &Model) -> Vec<String> {
    let mut names: Vec<String> = model.materials.iter()
        .map(|m| resolve(m).to_string())
        .collect();
    names.sort_unstable();
    names.dedup();
    names
}

fn resolve(material: &str) -> &str {
    if material.is_empty() { FALLBACK_TEXTURE } else { material }
}

/// Alle sichtbaren Objekte zu einem Mesh. `tex_names` gibt die Textur-
/// Indizes vor — darf eine Obermenge sein, so teilen alle Modelle eine
/// gemeinsame Texturliste.
pub fn build(model: &Model, tex_names: &[String]) -> Mesh {
    let mut verts = Vec::new();
    let mut per_tex: Vec<Vec<u32>> = vec![Vec::new(); tex_names.len()];

    for o in &model.objects {
        if !o.visible { continue; }
        let base = verts.len() as u32;
        for (p, uv) in o.verts.iter().zip(&o.uvs) {
            verts.push(Vertex { pos: *p, uv: *uv });
        }
        for (t, &m) in o.tris.iter().zip(&o.tri_mats) {
            let name = resolve(&model.materials[m]);
            let Some(ti) = tex_names.iter().position(|n| n == name) else { continue; };
            per_tex[ti].extend(t.map(|i| base + i as u32));
        }
    }

    // Pro-Textur-Indexlisten zu einem Buffer + Batches verflachen.
    let mut indices = Vec::new();
    let mut batches = Vec::new();
    for (ti, list) in per_tex.into_iter().enumerate() {
        if list.is_empty() { continue; }
        batches.push(Batch { texture: ti, start: indices.len() as u32, count: list.len() as u32 });
        indices.extend_from_slice(&list);
    }
    Mesh { verts, indices, batches }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::engine::model::{Model, Object, Props};

    /// Ein Dreieck mit UVs; `visible` steuert, ob es gerendert werden darf.
    fn tri(name: &str, visible: bool) -> Object {
        Object {
            name: name.into(),
            verts: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
            uvs: vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]],
            tris: vec![[0, 1, 2]],
            tri_mats: vec![0],
            visible, collider: !visible,
            props: Props::new(),
        }
    }

    fn sample() -> Model {
        Model {
            objects: vec![tri("Ding", true), tri("Proxy", false)],
            empties: Vec::new(),
            materials: vec!["carpet".to_string()],
        }
    }

    #[test]
    fn builds_only_visible_objects() {
        let m = sample();
        let names = texture_names(&m);
        assert_eq!(names, vec!["carpet".to_string()]);
        let mesh = build(&m, &names);

        // Nur „Ding" (der Proxy ist unsichtbar): 3 Vertices, 1 Dreieck.
        assert_eq!(mesh.verts.len(), 3);
        assert_eq!(mesh.indices, vec![0, 1, 2]);
        assert_eq!(mesh.batches.len(), 1);
        // UVs unverändert (geteilte Oben-links-Konvention).
        assert_eq!(mesh.verts[2].uv, [1.0, 1.0]);
    }

    #[test]
    fn missing_material_maps_to_fallback() {
        let mut m = sample();
        m.materials = vec![String::new()]; // Faces ohne Material
        assert_eq!(texture_names(&m), vec![FALLBACK_TEXTURE.to_string()]);
        let mesh = build(&m, &texture_names(&m));
        assert_eq!(mesh.batches.len(), 1);
    }

    #[test]
    fn shares_texture_index_space_with_superset_list() {
        // Obermengen-Liste (wie beim Merge mehrerer Modelle): der Batch
        // zeigt auf den richtigen Index in der geteilten Liste.
        let names = vec!["aaa".to_string(), "carpet".to_string(), "zzz".to_string()];
        let mesh = build(&sample(), &names);
        assert_eq!(mesh.batches[0].texture, 1);
    }
}
