use crate::ecs::animation::components::{
    AnimationChannel, AnimationClip, AnimationInterpolation, AnimationProperty, AnimationSampler,
    AnimationSamplerOutput,
};
use crate::ecs::material::components::AlphaMode;
use crate::ecs::mesh::components::{
    Mesh, MorphTarget, MorphTargetData, SkinData, SkinnedVertex, Vertex,
};
use crate::ecs::world::components::{
    Camera, LocalTransform, Material, Name, OrthographicCamera, PerspectiveCamera, Projection,
    RenderMesh, Visibility,
};
use gltf::{Document, Node, Primitive, buffer, import, import_slice};
use nalgebra_glm::{Mat4, Quat, Vec2, Vec3, vec2, vec3};
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

use super::super::components::{Prefab, PrefabComponents, PrefabNode};

#[derive(Debug, Clone)]
pub struct GltfSkin {
    pub name: Option<String>,
    pub joints: Vec<usize>,
    pub inverse_bind_matrices: Vec<Mat4>,
}

pub struct GltfLoadResult {
    pub prefabs: Vec<Prefab>,
    pub meshes: HashMap<String, Mesh>,
    pub materials: Vec<Material>,
    pub textures: HashMap<String, (Vec<u8>, u32, u32)>,
    pub animations: Vec<AnimationClip>,
    pub skins: Vec<GltfSkin>,
    pub node_to_skin: HashMap<usize, usize>,
    pub node_to_morph_target_count: HashMap<usize, usize>,
    pub node_count: usize,
}

pub fn import_gltf_from_path(
    path: &std::path::Path,
) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
    let (doc, buffers, images) = import(path)?;

    let canonical_path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
    let namespace = canonical_path
        .to_str()
        .unwrap_or("unnamed")
        .replace('\\', "/")
        .replace('/', "::");

    process_gltf_data(doc, &buffers, &images, &namespace)
}

pub fn import_gltf_from_bytes(bytes: &[u8]) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
    let (doc, buffers, images) = import_slice(bytes)?;
    let mut hasher = DefaultHasher::new();
    bytes.hash(&mut hasher);
    let namespace = format!("bytes_{:016x}", hasher.finish());
    process_gltf_data(doc, &buffers, &images, &namespace)
}

fn process_gltf_data(
    doc: Document,
    buffers: &[buffer::Data],
    images: &[gltf::image::Data],
    namespace: &str,
) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
    let mut meshes = HashMap::new();
    let mut materials = Vec::new();
    let mut textures = HashMap::new();
    let mut mesh_index_to_primitives: HashMap<usize, Vec<(String, usize)>> = HashMap::new();
    let mut gltf_material_index_to_deduplicated_index: HashMap<usize, usize> = HashMap::new();

    let mut mesh_to_skin: HashMap<usize, usize> = HashMap::new();
    let mut node_to_morph_target_count: HashMap<usize, usize> = HashMap::new();
    for node in doc.nodes() {
        if let (Some(mesh_index), Some(skin_index)) = (
            node.mesh().map(|m| m.index()),
            node.skin().map(|s| s.index()),
        ) {
            mesh_to_skin.insert(mesh_index, skin_index);
        }
        if let Some(mesh) = node.mesh() {
            let morph_count = mesh
                .primitives()
                .next()
                .map(|p| p.morph_targets().count())
                .unwrap_or(0);
            if morph_count > 0 {
                node_to_morph_target_count.insert(node.index(), morph_count);
            }
        }
    }

    let mut image_index_to_name = HashMap::new();
    for image in doc.images() {
        let base_name = image
            .name()
            .unwrap_or(&format!("Image_{}", image.index()))
            .to_string();
        let name = format!("{}::{}", namespace, base_name);
        image_index_to_name.insert(image.index(), name);
    }

    for material in doc.materials() {
        let pbr = material.pbr_metallic_roughness();
        let base_color = pbr.base_color_factor();
        let emissive = material.emissive_factor();

        let (base_texture, base_texture_uv_set) = pbr
            .base_color_texture()
            .map(|tex| {
                let image_index = tex.texture().source().index();
                (
                    image_index_to_name.get(&image_index).cloned(),
                    tex.tex_coord(),
                )
            })
            .unwrap_or((None, 0));

        let (emissive_texture, emissive_texture_uv_set) = material
            .emissive_texture()
            .map(|tex| {
                let image_index = tex.texture().source().index();
                (
                    image_index_to_name.get(&image_index).cloned(),
                    tex.tex_coord(),
                )
            })
            .unwrap_or((None, 0));

        let (normal_texture, normal_texture_uv_set) = material
            .normal_texture()
            .map(|tex| {
                let image_index = tex.texture().source().index();
                (
                    image_index_to_name.get(&image_index).cloned(),
                    tex.tex_coord(),
                )
            })
            .unwrap_or((None, 0));

        let normal_scale = material
            .normal_texture()
            .map(|tex| tex.scale())
            .unwrap_or(1.0);

        let (metallic_roughness_texture, metallic_roughness_texture_uv_set) = pbr
            .metallic_roughness_texture()
            .map(|tex| {
                let image_index = tex.texture().source().index();
                (
                    image_index_to_name.get(&image_index).cloned(),
                    tex.tex_coord(),
                )
            })
            .unwrap_or((None, 0));

        let (occlusion_texture, occlusion_texture_uv_set) = material
            .occlusion_texture()
            .map(|tex| {
                let image_index = tex.texture().source().index();
                (
                    image_index_to_name.get(&image_index).cloned(),
                    tex.tex_coord(),
                )
            })
            .unwrap_or((None, 0));

        let occlusion_strength = material
            .occlusion_texture()
            .map(|tex| tex.strength())
            .unwrap_or(1.0);

        let transmission_factor = material
            .transmission()
            .map(|t| t.transmission_factor())
            .unwrap_or(0.0);

        let (transmission_texture, transmission_texture_uv_set) = material
            .transmission()
            .and_then(|t| {
                t.transmission_texture().map(|tex| {
                    let image_index = tex.texture().source().index();
                    (
                        image_index_to_name.get(&image_index).cloned(),
                        tex.tex_coord(),
                    )
                })
            })
            .unwrap_or((None, 0));

        let thickness = material
            .volume()
            .map(|v| v.thickness_factor())
            .unwrap_or(0.0);

        let (thickness_texture, thickness_texture_uv_set) = material
            .volume()
            .and_then(|v| {
                v.thickness_texture().map(|tex| {
                    let image_index = tex.texture().source().index();
                    (
                        image_index_to_name.get(&image_index).cloned(),
                        tex.tex_coord(),
                    )
                })
            })
            .unwrap_or((None, 0));

        let attenuation_color = material
            .volume()
            .map(|v| v.attenuation_color())
            .unwrap_or([1.0, 1.0, 1.0]);

        let attenuation_distance = material
            .volume()
            .map(|v| v.attenuation_distance())
            .unwrap_or(f32::INFINITY);

        let ior = material.ior().unwrap_or(1.5);

        let specular_factor = material
            .specular()
            .map(|s| s.specular_factor())
            .unwrap_or(1.0);

        let specular_color_factor = material
            .specular()
            .map(|s| s.specular_color_factor())
            .unwrap_or([1.0, 1.0, 1.0]);

        let (specular_texture, specular_texture_uv_set) = material
            .specular()
            .and_then(|s| {
                s.specular_texture().map(|tex| {
                    let image_index = tex.texture().source().index();
                    (
                        image_index_to_name.get(&image_index).cloned(),
                        tex.tex_coord(),
                    )
                })
            })
            .unwrap_or((None, 0));

        let (specular_color_texture, specular_color_texture_uv_set) = material
            .specular()
            .and_then(|s| {
                s.specular_color_texture().map(|tex| {
                    let image_index = tex.texture().source().index();
                    (
                        image_index_to_name.get(&image_index).cloned(),
                        tex.tex_coord(),
                    )
                })
            })
            .unwrap_or((None, 0));

        let emissive_strength = material.emissive_strength().unwrap_or(1.0);

        let world_material = Material {
            base_color: [base_color[0], base_color[1], base_color[2], base_color[3]],
            emissive_factor: [emissive[0], emissive[1], emissive[2]],
            alpha_mode: match material.alpha_mode() {
                gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
                gltf::material::AlphaMode::Mask => AlphaMode::Mask,
                gltf::material::AlphaMode::Blend => AlphaMode::Blend,
            },
            alpha_cutoff: material.alpha_cutoff().unwrap_or(0.5),
            base_texture,
            base_texture_uv_set,
            emissive_texture,
            emissive_texture_uv_set,
            normal_texture,
            normal_texture_uv_set,
            normal_scale,
            normal_map_flip_y: false,
            normal_map_two_component: false,
            metallic_roughness_texture,
            metallic_roughness_texture_uv_set,
            occlusion_texture,
            occlusion_texture_uv_set,
            occlusion_strength,
            roughness: pbr.roughness_factor(),
            metallic: pbr.metallic_factor(),
            unlit: material.unlit(),
            double_sided: material.double_sided(),
            uv_scale: [1.0, 1.0],
            transmission_factor,
            transmission_texture,
            transmission_texture_uv_set,
            thickness,
            thickness_texture,
            thickness_texture_uv_set,
            attenuation_color,
            attenuation_distance,
            ior,
            specular_factor,
            specular_color_factor,
            specular_texture,
            specular_texture_uv_set,
            specular_color_texture,
            specular_color_texture_uv_set,
            emissive_strength,
        };

        let deduplicated_index =
            if let Some(existing_index) = materials.iter().position(|m| m == &world_material) {
                if let Some(mat_index) = material.index() {
                    tracing::debug!(
                        "Material {} is identical to material {}, reusing",
                        mat_index,
                        existing_index
                    );
                }
                existing_index
            } else {
                let new_index = materials.len();
                materials.push(world_material);
                new_index
            };

        if let Some(mat_index) = material.index() {
            gltf_material_index_to_deduplicated_index.insert(mat_index, deduplicated_index);
        }
    }

    for mesh in doc.meshes() {
        let base_mesh_name = mesh
            .name()
            .unwrap_or(&format!("Mesh_{}", mesh.index()))
            .to_string();

        let mut primitive_names = Vec::new();
        let default_morph_weights: Option<Vec<f32>> = mesh.weights().map(|w| w.to_vec());

        for (primitive_index, primitive) in mesh.primitives().enumerate() {
            if let Some(mut mesh_primitive) =
                load_gltf_primitive(&primitive, buffers, default_morph_weights.as_deref())
            {
                if let Some(&skin_index) = mesh_to_skin.get(&mesh.index())
                    && let Some(skin_data) = &mut mesh_primitive.skin_data
                {
                    skin_data.skin_index = Some(skin_index);
                }

                let base_primitive_name = if mesh.primitives().len() > 1 {
                    format!("{}_{}", base_mesh_name, primitive_index)
                } else {
                    base_mesh_name.clone()
                };
                let primitive_name = format!("{}::{}", namespace, base_primitive_name);

                let gltf_material_index = primitive.material().index().unwrap_or(0);
                let material_index = gltf_material_index_to_deduplicated_index
                    .get(&gltf_material_index)
                    .copied()
                    .unwrap_or(0);
                primitive_names.push((primitive_name.clone(), material_index));
                meshes.insert(primitive_name, mesh_primitive);
            }
        }

        if !primitive_names.is_empty() {
            mesh_index_to_primitives.insert(mesh.index(), primitive_names);
        }
    }

    let mut prefabs = Vec::new();

    for scene in doc.scenes() {
        let scene_name = scene
            .name()
            .unwrap_or(&format!("Scene_{}", scene.index()))
            .to_string();

        let mut root_nodes = Vec::new();

        for node in scene.nodes() {
            convert_node_to_prefab_nodes(
                &node,
                &mesh_index_to_primitives,
                &materials,
                &mut root_nodes,
            );
        }

        prefabs.push(Prefab {
            name: scene_name,
            root_nodes,
        });
    }

    for image in doc.images() {
        if let Some(image_data) = images.get(image.index()) {
            let base_name = image
                .name()
                .unwrap_or(&format!("Image_{}", image.index()))
                .to_string();
            let name = format!("{}::{}", namespace, base_name);

            let (width, height) = match image_data.format {
                gltf::image::Format::R8G8B8 => {
                    let mut rgba_data = Vec::with_capacity(image_data.pixels.len() / 3 * 4);
                    for chunk in image_data.pixels.chunks(3) {
                        rgba_data.push(chunk[0]);
                        rgba_data.push(chunk[1]);
                        rgba_data.push(chunk[2]);
                        rgba_data.push(255);
                    }
                    textures.insert(name, (rgba_data, image_data.width, image_data.height));
                    continue;
                }
                gltf::image::Format::R8G8B8A8 => (image_data.width, image_data.height),
                _ => {
                    tracing::warn!("Unsupported texture format for {}", name);
                    continue;
                }
            };

            textures.insert(name, (image_data.pixels.clone(), width, height));
        }
    }

    let animations: Vec<AnimationClip> = doc
        .animations()
        .map(|animation| load_gltf_animation(&animation, buffers, &node_to_morph_target_count))
        .collect();

    let skins: Vec<GltfSkin> = doc
        .skins()
        .map(|skin| load_gltf_skin(&skin, buffers))
        .collect();

    let mut node_to_skin: HashMap<usize, usize> = HashMap::new();
    for node in doc.nodes() {
        if let Some(skin) = node.skin() {
            node_to_skin.insert(node.index(), skin.index());
        }
    }

    let node_count = doc.nodes().count();

    Ok(GltfLoadResult {
        prefabs,
        meshes,
        materials,
        textures,
        animations,
        skins,
        node_to_skin,
        node_to_morph_target_count,
        node_count,
    })
}

fn convert_node_to_prefab_nodes(
    node: &Node,
    mesh_index_to_primitives: &HashMap<usize, Vec<(String, usize)>>,
    materials: &[Material],
    output_nodes: &mut Vec<PrefabNode>,
) {
    let (translation, rotation, scale) = node.transform().decomposed();

    let quat = Quat::new(rotation[3], rotation[0], rotation[1], rotation[2]);

    let local_transform = LocalTransform {
        translation: Vec3::new(translation[0], translation[1], translation[2]),
        rotation: quat,
        scale: Vec3::new(scale[0], scale[1], scale[2]),
    };

    let node_name = node.name().map(|s| s.to_string());

    let node_skin_index = node.skin().map(|s| s.index());

    if let Some(mesh) = node.mesh() {
        if let Some(primitives) = mesh_index_to_primitives.get(&mesh.index()) {
            if primitives.len() == 1 {
                let (mesh_name, material_index) = &primitives[0];
                let mut components = PrefabComponents::default();

                if let Some(name_str) = &node_name {
                    components.name = Some(Name(name_str.clone()));
                }

                components.render_mesh = Some(RenderMesh::new(mesh_name.clone()));

                components.material = Some(
                    materials
                        .get(*material_index)
                        .cloned()
                        .unwrap_or_else(Material::default),
                );

                if let Some(camera) = node.camera() {
                    components.camera = Some(load_gltf_camera(&camera));
                }

                components.visibility = Some(Visibility { visible: true });
                components.skin_index = node_skin_index;

                let mut children = Vec::new();
                for child in node.children() {
                    convert_node_to_prefab_nodes(
                        &child,
                        mesh_index_to_primitives,
                        materials,
                        &mut children,
                    );
                }

                output_nodes.push(PrefabNode {
                    local_transform,
                    components,
                    children,
                    node_index: Some(node.index()),
                });
            } else {
                let mut parent_components = PrefabComponents::default();
                if let Some(name_str) = &node_name {
                    parent_components.name = Some(Name(name_str.clone()));
                }

                if let Some(camera) = node.camera() {
                    parent_components.camera = Some(load_gltf_camera(&camera));
                }

                parent_components.skin_index = node_skin_index;

                let mut children = Vec::new();

                for (index, (mesh_name, material_index)) in primitives.iter().enumerate() {
                    let mut primitive_components = PrefabComponents {
                        name: Some(Name(format!("Primitive_{}", index))),
                        render_mesh: Some(RenderMesh::new(mesh_name.clone())),
                        visibility: Some(Visibility { visible: true }),
                        skin_index: node_skin_index,
                        ..Default::default()
                    };

                    primitive_components.material = Some(
                        materials
                            .get(*material_index)
                            .cloned()
                            .unwrap_or_else(Material::default),
                    );

                    children.push(PrefabNode {
                        local_transform: LocalTransform::default(),
                        components: primitive_components,
                        children: Vec::new(),
                        node_index: None,
                    });
                }

                for child in node.children() {
                    convert_node_to_prefab_nodes(
                        &child,
                        mesh_index_to_primitives,
                        materials,
                        &mut children,
                    );
                }

                output_nodes.push(PrefabNode {
                    local_transform,
                    components: parent_components,
                    children,
                    node_index: Some(node.index()),
                });
            }
        } else {
            let mut components = PrefabComponents::default();
            if let Some(name_str) = &node_name {
                components.name = Some(Name(name_str.clone()));
            }

            if let Some(camera) = node.camera() {
                components.camera = Some(load_gltf_camera(&camera));
            }

            components.skin_index = node_skin_index;

            let mut children = Vec::new();
            for child in node.children() {
                convert_node_to_prefab_nodes(
                    &child,
                    mesh_index_to_primitives,
                    materials,
                    &mut children,
                );
            }

            output_nodes.push(PrefabNode {
                local_transform,
                components,
                children,
                node_index: Some(node.index()),
            });
        }
    } else {
        let mut components = PrefabComponents::default();
        if let Some(name_str) = &node_name {
            components.name = Some(Name(name_str.clone()));
        }

        if let Some(camera) = node.camera() {
            components.camera = Some(load_gltf_camera(&camera));
        }

        components.skin_index = node_skin_index;

        let mut children = Vec::new();
        for child in node.children() {
            convert_node_to_prefab_nodes(
                &child,
                mesh_index_to_primitives,
                materials,
                &mut children,
            );
        }

        output_nodes.push(PrefabNode {
            local_transform,
            components,
            children,
            node_index: Some(node.index()),
        });
    }
}

fn load_gltf_camera(camera: &gltf::Camera<'_>) -> Camera {
    let projection = match camera.projection() {
        gltf::camera::Projection::Perspective(persp) => {
            Projection::Perspective(PerspectiveCamera {
                aspect_ratio: persp.aspect_ratio(),
                y_fov_rad: persp.yfov(),
                z_far: persp.zfar(),
                z_near: persp.znear(),
            })
        }
        gltf::camera::Projection::Orthographic(ortho) => {
            Projection::Orthographic(OrthographicCamera {
                x_mag: ortho.xmag(),
                y_mag: ortho.ymag(),
                z_far: ortho.zfar(),
                z_near: ortho.znear(),
            })
        }
    };

    Camera {
        projection,
        smoothing: None,
    }
}

fn load_gltf_primitive(
    primitive: &Primitive,
    buffers: &[buffer::Data],
    default_morph_weights: Option<&[f32]>,
) -> Option<Mesh> {
    let reader = primitive.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));

    let positions: Vec<Vec3> = reader
        .read_positions()?
        .map(|p| vec3(p[0], p[1], p[2]))
        .collect();

    if positions.is_empty() {
        tracing::warn!("glTF primitive has no positions, skipping");
        return None;
    }

    let normals: Vec<Vec3> = if let Some(normals_iter) = reader.read_normals() {
        let normals: Vec<_> = normals_iter.map(|n| vec3(n[0], n[1], n[2])).collect();

        if normals.len() != positions.len() {
            tracing::warn!("glTF primitive has mismatched normal count, generating normals");
            generate_normals(&positions, &reader.read_indices())
        } else {
            normals
        }
    } else {
        generate_normals(&positions, &reader.read_indices())
    };

    let mut texcoords: Vec<Vec2> = vec![vec2(0.0, 0.0); positions.len()];

    if let Some(tex_iter) = reader.read_tex_coords(0) {
        let coords: Vec<_> = tex_iter.into_f32().map(|t| vec2(t[0], t[1])).collect();

        if coords.len() == positions.len() {
            texcoords = coords;
        } else {
            tracing::warn!("glTF primitive has mismatched texcoord count, using defaults");
        }
    }

    let mut texcoords_1: Vec<Vec2> = vec![vec2(0.0, 0.0); positions.len()];

    if let Some(tex_iter) = reader.read_tex_coords(1) {
        let coords: Vec<_> = tex_iter.into_f32().map(|t| vec2(t[0], t[1])).collect();

        if coords.len() == positions.len() {
            texcoords_1 = coords;
        }
    }

    let colors: Vec<[f32; 4]> = if let Some(color_iter) = reader.read_colors(0) {
        let color_data: Vec<[f32; 4]> = color_iter.into_rgba_f32().collect();
        if color_data.len() == positions.len() {
            color_data
        } else {
            vec![[1.0, 1.0, 1.0, 1.0]; positions.len()]
        }
    } else {
        vec![[1.0, 1.0, 1.0, 1.0]; positions.len()]
    };

    let tangents: Vec<[f32; 4]> = if let Some(tangent_iter) = reader.read_tangents() {
        let tangent_data: Vec<[f32; 4]> = tangent_iter.collect();
        if tangent_data.len() == positions.len() {
            tangent_data
        } else {
            vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]
        }
    } else {
        vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]
    };

    let has_joints = reader.read_joints(0).is_some();
    let skin_data = if has_joints {
        let mut joint_indices: Vec<[u32; 4]> = vec![[0, 0, 0, 0]; positions.len()];

        if let Some(joints_iter) = reader.read_joints(0) {
            let joints: Vec<[u16; 4]> = joints_iter.into_u16().collect();
            if joints.len() == positions.len() {
                for (vertex_index, joints) in joints.iter().enumerate() {
                    joint_indices[vertex_index] = [
                        joints[0] as u32,
                        joints[1] as u32,
                        joints[2] as u32,
                        joints[3] as u32,
                    ];
                }
            }
        }

        let joint_weights: Vec<[f32; 4]> = if let Some(weights_iter) = reader.read_weights(0) {
            let weights: Vec<[f32; 4]> = weights_iter.into_f32().collect();
            if weights.len() == positions.len() {
                weights
                    .into_iter()
                    .map(|w| {
                        let sum = w[0] + w[1] + w[2] + w[3];
                        if sum > 0.0 {
                            [w[0] / sum, w[1] / sum, w[2] / sum, w[3] / sum]
                        } else {
                            [1.0, 0.0, 0.0, 0.0]
                        }
                    })
                    .collect()
            } else {
                vec![[1.0, 0.0, 0.0, 0.0]; positions.len()]
            }
        } else {
            vec![[1.0, 0.0, 0.0, 0.0]; positions.len()]
        };

        let skinned_vertices: Vec<SkinnedVertex> = positions
            .iter()
            .zip(normals.iter())
            .zip(texcoords.iter())
            .zip(texcoords_1.iter())
            .zip(tangents.iter())
            .zip(colors.iter())
            .zip(joint_indices.iter())
            .zip(joint_weights.iter())
            .map(
                |(
                    (
                        (((((position, normal), tex_coords), tex_coords_1), tangent), color),
                        joint_indices,
                    ),
                    joint_weights,
                )| {
                    SkinnedVertex {
                        position: [position.x, position.y, position.z],
                        normal: [normal.x, normal.y, normal.z],
                        tex_coords: [tex_coords.x, tex_coords.y],
                        tex_coords_1: [tex_coords_1.x, tex_coords_1.y],
                        tangent: *tangent,
                        color: *color,
                        joint_indices: *joint_indices,
                        joint_weights: *joint_weights,
                    }
                },
            )
            .collect();

        Some(SkinData::new(skinned_vertices))
    } else {
        None
    };

    let indices: Vec<u32> = if let Some(indices_iter) = reader.read_indices() {
        let mut indices: Vec<u32> = indices_iter.into_u32().collect();

        if !indices.len().is_multiple_of(3) {
            tracing::warn!("glTF primitive has non-triangular indices, truncating");
            indices.truncate((indices.len() / 3) * 3);
        }

        indices
    } else if positions.len().is_multiple_of(3) {
        (0..positions.len() as u32).collect()
    } else {
        tracing::warn!(
            "glTF primitive without indices has non-triangular vertex count, truncating"
        );
        let vertex_count = (positions.len() / 3) * 3;
        (0..vertex_count as u32).collect()
    };

    if indices.is_empty() {
        tracing::warn!("glTF primitive has no valid indices, skipping");
        return None;
    }

    if positions.len() != normals.len() || positions.len() != texcoords.len() {
        tracing::error!("glTF primitive has inconsistent vertex attribute lengths");
        return None;
    }

    let vertices: Vec<Vertex> = positions
        .iter()
        .zip(&normals)
        .zip(&texcoords)
        .zip(&texcoords_1)
        .zip(&tangents)
        .zip(&colors)
        .map(
            |(((((position, normal), tex_coords), tex_coords_1), tangent), color)| Vertex {
                position: [position.x, position.y, position.z],
                normal: [normal.x, normal.y, normal.z],
                tex_coords: [tex_coords.x, tex_coords.y],
                tex_coords_1: [tex_coords_1.x, tex_coords_1.y],
                tangent: *tangent,
                color: *color,
            },
        )
        .collect();

    let mut min = nalgebra_glm::vec3(f32::MAX, f32::MAX, f32::MAX);
    let mut max = nalgebra_glm::vec3(f32::MIN, f32::MIN, f32::MIN);

    for position in &positions {
        min.x = min.x.min(position.x);
        min.y = min.y.min(position.y);
        min.z = min.z.min(position.z);

        max.x = max.x.max(position.x);
        max.y = max.y.max(position.y);
        max.z = max.z.max(position.z);
    }

    let center = (min + max) * 0.5;
    let half_extents = (max - min) * 0.5;
    let sphere_radius = nalgebra_glm::length(&half_extents);

    let bounding_volume = crate::ecs::bounding_volume::components::BoundingVolume {
        obb: crate::ecs::bounding_volume::components::OrientedBoundingBox {
            center,
            half_extents,
            orientation: nalgebra_glm::quat_identity(),
        },
        sphere_radius,
    };

    let morph_targets = load_morph_targets(
        primitive,
        buffers,
        &positions,
        &normals,
        default_morph_weights,
    );

    let mut mesh = Mesh::with_bounding_volume(vertices, indices, bounding_volume);
    mesh.skin_data = skin_data;
    mesh.morph_targets = morph_targets;

    Some(mesh)
}

fn load_morph_targets(
    primitive: &Primitive,
    buffers: &[buffer::Data],
    positions: &[Vec3],
    normals: &[Vec3],
    default_weights: Option<&[f32]>,
) -> Option<MorphTargetData> {
    let reader = primitive.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));
    let vertex_count = positions.len();

    let morph_target_iter = reader.read_morph_targets();
    let targets: Vec<MorphTarget> = morph_target_iter
        .filter_map(|morph_target| {
            let position_displacements: Vec<[f32; 3]> =
                morph_target.0?.map(|p| [p[0], p[1], p[2]]).collect();

            if position_displacements.len() != vertex_count {
                return None;
            }

            let mut target = MorphTarget::new(position_displacements);

            if let Some(normals_iter) = morph_target.1 {
                let normal_displacements: Vec<[f32; 3]> =
                    normals_iter.map(|n| [n[0], n[1], n[2]]).collect();
                if normal_displacements.len() == vertex_count {
                    target = target.with_normals(normal_displacements);
                }
            }

            if let Some(tangents_iter) = morph_target.2 {
                let tangent_displacements: Vec<[f32; 3]> =
                    tangents_iter.map(|t| [t[0], t[1], t[2]]).collect();
                if tangent_displacements.len() == vertex_count {
                    target = target.with_tangents(tangent_displacements);
                }
            }

            Some(target)
        })
        .collect();

    if targets.is_empty() {
        None
    } else {
        let base_positions: Vec<[f32; 3]> = positions.iter().map(|p| [p.x, p.y, p.z]).collect();
        let base_normals: Vec<[f32; 3]> = normals.iter().map(|n| [n.x, n.y, n.z]).collect();
        let weights = match default_weights {
            Some(w) => w.iter().take(targets.len()).copied().collect(),
            None => vec![0.0; targets.len()],
        };
        Some(
            MorphTargetData::new(targets)
                .with_base_data(base_positions, base_normals)
                .with_default_weights(weights),
        )
    }
}

fn generate_normals(
    positions: &[Vec3],
    indices_reader: &Option<gltf::mesh::util::ReadIndices>,
) -> Vec<Vec3> {
    let mut normals = vec![Vec3::zeros(); positions.len()];
    let mut face_counts = vec![0u32; positions.len()];

    let indices: Vec<u32> = if let Some(indices_iter) = indices_reader {
        indices_iter.clone().into_u32().collect()
    } else {
        (0..positions.len() as u32).collect()
    };

    for triangle in indices.chunks(3) {
        if triangle.len() != 3 {
            continue;
        }

        let i0 = triangle[0] as usize;
        let i1 = triangle[1] as usize;
        let i2 = triangle[2] as usize;

        if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
            continue;
        }

        let v0 = positions[i0];
        let v1 = positions[i1];
        let v2 = positions[i2];

        let edge1 = v1 - v0;
        let edge2 = v2 - v0;
        let face_normal = nalgebra_glm::cross(&edge1, &edge2);

        let face_normal = if nalgebra_glm::length(&face_normal) > 1e-6 {
            nalgebra_glm::normalize(&face_normal)
        } else {
            vec3(0.0, 1.0, 0.0)
        };

        normals[i0] += face_normal;
        normals[i1] += face_normal;
        normals[i2] += face_normal;

        face_counts[i0] += 1;
        face_counts[i1] += 1;
        face_counts[i2] += 1;
    }

    for (index, normal) in normals.iter_mut().enumerate() {
        if face_counts[index] > 0 {
            *normal /= face_counts[index] as f32;
            let magnitude = nalgebra_glm::length(normal);
            if magnitude > 1e-6 {
                *normal = nalgebra_glm::normalize(normal);
            } else {
                *normal = vec3(0.0, 1.0, 0.0);
            }
        } else {
            *normal = vec3(0.0, 1.0, 0.0);
        }
    }

    normals
}

fn load_gltf_animation(
    animation: &gltf::Animation,
    buffers: &[buffer::Data],
    node_to_morph_target_count: &HashMap<usize, usize>,
) -> AnimationClip {
    let mut channels = Vec::new();

    for (channel_index, channel) in animation.channels().enumerate() {
        let reader = channel.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));

        let target_gltf_node = channel.target().node();
        let target_node = target_gltf_node.index();
        let target_bone_name = target_gltf_node.name().map(|s| s.to_string());
        let target_property = match channel.target().property() {
            gltf::animation::Property::Translation => AnimationProperty::Translation,
            gltf::animation::Property::Rotation => AnimationProperty::Rotation,
            gltf::animation::Property::Scale => AnimationProperty::Scale,
            gltf::animation::Property::MorphTargetWeights => AnimationProperty::MorphWeights,
        };

        let interpolation = match channel.sampler().interpolation() {
            gltf::animation::Interpolation::Linear => AnimationInterpolation::Linear,
            gltf::animation::Interpolation::Step => AnimationInterpolation::Step,
            gltf::animation::Interpolation::CubicSpline => AnimationInterpolation::CubicSpline,
        };

        let Some(inputs) = reader.read_inputs() else {
            tracing::warn!("  Channel {}: skipping - no input data", channel_index);
            continue;
        };
        let input: Vec<f32> = inputs.collect();

        let Some(outputs) = reader.read_outputs() else {
            tracing::warn!("  Channel {}: skipping - no output data", channel_index);
            continue;
        };

        let output = match outputs {
            gltf::animation::util::ReadOutputs::Translations(translations) => {
                if interpolation == AnimationInterpolation::CubicSpline {
                    let all_data: Vec<[f32; 3]> = translations.collect();
                    let num_keyframes = all_data.len() / 3;

                    let mut in_tangents = Vec::with_capacity(num_keyframes);
                    let mut values = Vec::with_capacity(num_keyframes);
                    let mut out_tangents = Vec::with_capacity(num_keyframes);

                    for keyframe_index in 0..num_keyframes {
                        let base_idx = keyframe_index * 3;
                        in_tangents.push(vec3(
                            all_data[base_idx][0],
                            all_data[base_idx][1],
                            all_data[base_idx][2],
                        ));
                        values.push(vec3(
                            all_data[base_idx + 1][0],
                            all_data[base_idx + 1][1],
                            all_data[base_idx + 1][2],
                        ));
                        out_tangents.push(vec3(
                            all_data[base_idx + 2][0],
                            all_data[base_idx + 2][1],
                            all_data[base_idx + 2][2],
                        ));
                    }

                    AnimationSamplerOutput::CubicSplineVec3 {
                        values,
                        in_tangents,
                        out_tangents,
                    }
                } else {
                    let values = translations.map(|t| vec3(t[0], t[1], t[2])).collect();
                    AnimationSamplerOutput::Vec3(values)
                }
            }
            gltf::animation::util::ReadOutputs::Rotations(rotations) => {
                if interpolation == AnimationInterpolation::CubicSpline {
                    let all_data: Vec<[f32; 4]> = rotations.into_f32().collect();
                    let num_keyframes = all_data.len() / 3;

                    let mut in_tangents = Vec::with_capacity(num_keyframes);
                    let mut values = Vec::with_capacity(num_keyframes);
                    let mut out_tangents = Vec::with_capacity(num_keyframes);

                    for keyframe_index in 0..num_keyframes {
                        let base_idx = keyframe_index * 3;
                        in_tangents.push(Quat::new(
                            all_data[base_idx][3],
                            all_data[base_idx][0],
                            all_data[base_idx][1],
                            all_data[base_idx][2],
                        ));
                        values.push(
                            Quat::new(
                                all_data[base_idx + 1][3],
                                all_data[base_idx + 1][0],
                                all_data[base_idx + 1][1],
                                all_data[base_idx + 1][2],
                            )
                            .normalize(),
                        );
                        out_tangents.push(Quat::new(
                            all_data[base_idx + 2][3],
                            all_data[base_idx + 2][0],
                            all_data[base_idx + 2][1],
                            all_data[base_idx + 2][2],
                        ));
                    }

                    AnimationSamplerOutput::CubicSplineQuat {
                        values,
                        in_tangents,
                        out_tangents,
                    }
                } else {
                    let values = rotations
                        .into_f32()
                        .map(|r| Quat::new(r[3], r[0], r[1], r[2]).normalize())
                        .collect();
                    AnimationSamplerOutput::Quat(values)
                }
            }
            gltf::animation::util::ReadOutputs::Scales(scales) => {
                if interpolation == AnimationInterpolation::CubicSpline {
                    let all_data: Vec<[f32; 3]> = scales.collect();
                    let num_keyframes = all_data.len() / 3;

                    let mut in_tangents = Vec::with_capacity(num_keyframes);
                    let mut values = Vec::with_capacity(num_keyframes);
                    let mut out_tangents = Vec::with_capacity(num_keyframes);

                    for keyframe_index in 0..num_keyframes {
                        let base_idx = keyframe_index * 3;
                        in_tangents.push(vec3(
                            all_data[base_idx][0],
                            all_data[base_idx][1],
                            all_data[base_idx][2],
                        ));
                        values.push(vec3(
                            all_data[base_idx + 1][0],
                            all_data[base_idx + 1][1],
                            all_data[base_idx + 1][2],
                        ));
                        out_tangents.push(vec3(
                            all_data[base_idx + 2][0],
                            all_data[base_idx + 2][1],
                            all_data[base_idx + 2][2],
                        ));
                    }

                    AnimationSamplerOutput::CubicSplineVec3 {
                        values,
                        in_tangents,
                        out_tangents,
                    }
                } else {
                    let values = scales.map(|s| vec3(s[0], s[1], s[2])).collect();
                    AnimationSamplerOutput::Vec3(values)
                }
            }
            gltf::animation::util::ReadOutputs::MorphTargetWeights(weights) => {
                let weight_count = node_to_morph_target_count
                    .get(&target_node)
                    .copied()
                    .unwrap_or(1);
                if weight_count == 0 {
                    continue;
                }

                if interpolation == AnimationInterpolation::CubicSpline {
                    let all_data: Vec<f32> = weights.into_f32().collect();
                    let elements_per_keyframe = weight_count * 3;
                    if !all_data.len().is_multiple_of(elements_per_keyframe) {
                        continue;
                    }
                    let num_keyframes = all_data.len() / elements_per_keyframe;

                    let mut in_tangents = Vec::with_capacity(num_keyframes);
                    let mut values = Vec::with_capacity(num_keyframes);
                    let mut out_tangents = Vec::with_capacity(num_keyframes);

                    for keyframe_index in 0..num_keyframes {
                        let base_idx = keyframe_index * elements_per_keyframe;
                        let in_tangent: Vec<f32> =
                            all_data[base_idx..base_idx + weight_count].to_vec();
                        let value: Vec<f32> =
                            all_data[base_idx + weight_count..base_idx + weight_count * 2].to_vec();
                        let out_tangent: Vec<f32> = all_data
                            [base_idx + weight_count * 2..base_idx + weight_count * 3]
                            .to_vec();
                        in_tangents.push(in_tangent);
                        values.push(value);
                        out_tangents.push(out_tangent);
                    }

                    AnimationSamplerOutput::CubicSplineWeights {
                        values,
                        in_tangents,
                        out_tangents,
                    }
                } else {
                    let all_data: Vec<f32> = weights.into_f32().collect();
                    if !all_data.len().is_multiple_of(weight_count) {
                        continue;
                    }
                    let values: Vec<Vec<f32>> = all_data
                        .chunks(weight_count)
                        .map(|chunk| chunk.to_vec())
                        .collect();
                    AnimationSamplerOutput::Weights(values)
                }
            }
        };

        let sampler = AnimationSampler {
            input,
            output,
            interpolation,
        };

        channels.push(AnimationChannel {
            target_node,
            target_bone_name,
            target_property,
            sampler,
        });
    }

    let duration = channels
        .iter()
        .flat_map(|channel| channel.sampler.input.last())
        .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
        .copied()
        .unwrap_or(0.0);

    AnimationClip {
        name: animation.name().unwrap_or("Animation").to_string(),
        duration,
        channels,
    }
}

fn load_gltf_skin(skin: &gltf::Skin, buffers: &[buffer::Data]) -> GltfSkin {
    let joints: Vec<usize> = skin.joints().map(|j| j.index()).collect();

    let mut inverse_bind_matrices = Vec::new();

    if let Some(accessor) = skin.inverse_bind_matrices() {
        let view = accessor
            .view()
            .expect("Inverse bind matrices accessor has no view");
        let buffer_index = view.buffer().index();

        if let Some(buffer_data) = buffers.get(buffer_index) {
            let buffer_bytes = &buffer_data.0;
            let view_offset = view.offset();
            let accessor_offset = accessor.offset();
            let start = view_offset + accessor_offset;
            let stride = view.stride().unwrap_or(64);

            for joint_index in 0..accessor.count() {
                let matrix_start = start + joint_index * stride;
                if matrix_start + 64 <= buffer_bytes.len() {
                    let mut floats = [0.0f32; 16];
                    for (float_index, float_value) in floats.iter_mut().enumerate() {
                        let byte_offset = matrix_start + float_index * 4;
                        let bytes = &buffer_bytes[byte_offset..byte_offset + 4];
                        *float_value = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
                    }

                    let matrix = Mat4::new(
                        floats[0], floats[4], floats[8], floats[12], floats[1], floats[5],
                        floats[9], floats[13], floats[2], floats[6], floats[10], floats[14],
                        floats[3], floats[7], floats[11], floats[15],
                    );
                    inverse_bind_matrices.push(matrix);
                }
            }
        }
    }

    while inverse_bind_matrices.len() < joints.len() {
        inverse_bind_matrices.push(Mat4::identity());
    }

    GltfSkin {
        name: skin.name().map(|s| s.to_string()),
        joints,
        inverse_bind_matrices,
    }
}
