//! Direct FBX mesh extraction for thumbnail studio (no texture deps).

use bevy::asset::RenderAssetUsages;
use bevy::camera::visibility::RenderLayers;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
use bevy_ufbx::mesh::group_faces_by_material;
use bevy_ufbx::utils::convert_matrix;

use super::ThumbnailModelSource;
use crate::assets::thumbnails::studio::THUMBNAIL_LAYER;

pub struct FbxThumbnailSource;

impl ThumbnailModelSource for FbxThumbnailSource {
    fn spawn_preview(
        &self,
        commands: &mut Commands,
        meshes: &mut Assets<Mesh>,
        materials: &mut Assets<StandardMaterial>,
        root: Entity,
        catalog_path: &str,
    ) -> bool {
        let Ok(bytes) = std::fs::read(catalog_path) else {
            warn!("FBX thumbnail: could not read {catalog_path}");
            return false;
        };

        let scene = match ufbx::load_memory(
            &bytes,
            ufbx::LoadOpts {
                target_unit_meters: 1.0,
                target_axes: ufbx::CoordinateAxes::right_handed_y_up(),
                ..Default::default()
            },
        ) {
            Ok(scene) => scene,
            Err(err) => {
                warn!("FBX thumbnail: failed to parse {catalog_path}: {err:?}");
                return false;
            }
        };

        let material = materials.add(StandardMaterial {
            base_color: Color::srgb(0.72, 0.72, 0.75),
            ..default()
        });

        let mut spawned = false;
        for node in scene.nodes.as_ref().iter() {
            let Some(mesh_ref) = node.mesh.as_ref() else {
                continue;
            };
            let ufbx_mesh = mesh_ref.as_ref();
            if ufbx_mesh.num_vertices == 0 || ufbx_mesh.faces.as_ref().is_empty() {
                continue;
            }

            for (_material_idx, indices) in group_faces_by_material(ufbx_mesh) {
                if indices.is_empty() {
                    continue;
                }

                let mesh_handle = meshes.add(build_mesh(ufbx_mesh, &indices));
                let transform = Transform::from_matrix(convert_matrix(&node.geometry_to_world));

                commands.entity(root).with_children(|parent| {
                    parent.spawn((
                        RenderLayers::layer(THUMBNAIL_LAYER),
                        Mesh3d(mesh_handle),
                        MeshMaterial3d(material.clone()),
                        transform,
                    ));
                });
                spawned = true;
            }
        }

        if !spawned {
            warn!("FBX thumbnail: no renderable meshes in {catalog_path}");
        }
        spawned
    }
}

fn build_mesh(ufbx_mesh: &ufbx::Mesh, indices: &[u32]) -> Mesh {
    let mut mesh = Mesh::new(
        PrimitiveTopology::TriangleList,
        RenderAssetUsages::default(),
    );

    let positions: Vec<[f32; 3]> = ufbx_mesh
        .vertex_position
        .values
        .as_ref()
        .iter()
        .map(|v| [v.x as f32, v.y as f32, v.z as f32])
        .collect();
    mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);

    if ufbx_mesh.vertex_normal.exists {
        let normals: Vec<[f32; 3]> = (0..ufbx_mesh.num_vertices)
            .map(|i| {
                let n = ufbx_mesh.vertex_normal[i];
                [n.x as f32, n.y as f32, n.z as f32]
            })
            .collect();
        mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
    }

    if ufbx_mesh.vertex_uv.exists {
        let uvs: Vec<[f32; 2]> = (0..ufbx_mesh.num_vertices)
            .map(|i| {
                let uv = ufbx_mesh.vertex_uv[i];
                [uv.x as f32, uv.y as f32]
            })
            .collect();
        mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
    }

    mesh.insert_indices(Indices::U32(indices.to_vec()));
    mesh
}
