use crate::ecs::animation::components::{AnimationClip, AnimationPlayer};
use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::material::resources::material_registry_insert;
use crate::ecs::morph::components::MorphWeights;
use crate::ecs::prefab::commands::gltf_import::GltfSkin;
use crate::ecs::skin::components::{Joint, Skin};
use crate::ecs::world::components::MaterialRef;
use crate::ecs::world::components::{GlobalTransform, LocalTransformDirty, Parent};
use crate::ecs::world::{
    ANIMATION_PLAYER, BOUNDING_VOLUME, CAMERA, CASTS_SHADOW, COLLIDER, GLOBAL_TRANSFORM, JOINT,
    LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, MATERIAL_REF, MORPH_WEIGHTS, NAME, PARENT,
    PREFAB_SOURCE, RENDER_MESH, RIGID_BODY, SCRIPT, SKIN, VISIBILITY, Vec3, World,
};
use crate::render::wgpu::texture_cache::texture_cache_add_reference;
use freecs::Entity;
use std::collections::HashMap;
use std::sync::Arc;

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

fn get_bounding_volume_from_mesh_name(world: &World, mesh_name: &str) -> Option<BoundingVolume> {
    let mesh = registry_entry_by_name(&world.resources.mesh_cache.registry, mesh_name)?;
    mesh.bounding_volume
}

pub fn spawn_prefab(world: &mut World, prefab: &Prefab, offset: Vec3) -> Entity {
    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
        1,
    )[0];

    world.set_name(
        parent_entity,
        crate::ecs::world::components::Name(prefab.name.clone()),
    );
    world.set_local_transform(
        parent_entity,
        crate::ecs::world::components::LocalTransform {
            translation: offset,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::vec3(1.0, 1.0, 1.0),
        },
    );
    world.set_global_transform(parent_entity, GlobalTransform::default());
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);

    for root_node in &prefab.root_nodes {
        spawn_prefab_node(world, root_node, Vec3::zeros(), Some(parent_entity));
    }

    parent_entity
}

pub fn spawn_prefab_node(
    world: &mut World,
    node: &PrefabNode,
    offset: Vec3,
    parent_entity: Option<Entity>,
) -> Entity {
    let calculated_bounding_volume = if node.components.bounding_volume.is_none() {
        if let Some(render_mesh) = &node.components.render_mesh {
            get_bounding_volume_from_mesh_name(world, &render_mesh.name)
        } else {
            None
        }
    } else {
        None
    };

    let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;

    if node.components.name.is_some() {
        component_mask |= NAME;
    }

    if node.components.render_mesh.is_some() {
        component_mask |= RENDER_MESH | CASTS_SHADOW;
    }

    if node.components.material.is_some() {
        component_mask |= MATERIAL_REF;
    }

    if node.components.camera.is_some() {
        component_mask |= CAMERA;
    }

    if node.components.visibility.is_some() {
        component_mask |= VISIBILITY;
    }

    if node.components.bounding_volume.is_some() || calculated_bounding_volume.is_some() {
        component_mask |= BOUNDING_VOLUME;
    }

    if node.components.rigid_body.is_some() {
        component_mask |= RIGID_BODY;
    }

    if node.components.collider.is_some() {
        component_mask |= COLLIDER;
    }

    if node.components.script.is_some() {
        component_mask |= SCRIPT;
    }

    if parent_entity.is_some() {
        component_mask |= PARENT;
    }

    let entity = world.spawn_entities(component_mask, 1)[0];

    let mut local_transform = node.local_transform;
    if parent_entity.is_none() {
        local_transform.translation += offset;
    }

    world.set_local_transform(entity, local_transform);
    world.set_global_transform(entity, GlobalTransform::default());
    world.set_local_transform_dirty(entity, LocalTransformDirty);

    if let Some(name) = &node.components.name {
        world.set_name(entity, name.clone());
    }

    if let Some(script) = &node.components.script {
        world.set_script(entity, script.clone());
    }

    if let Some(render_mesh) = &node.components.render_mesh {
        if !world
            .resources
            .mesh_cache
            .registry
            .name_to_index
            .contains_key(&render_mesh.name)
        {
            tracing::error!(
                "Mesh '{}' not found in mesh_cache when spawning entity {:?}. Skipping RenderMesh component.",
                render_mesh.name,
                entity
            );
        } else {
            if let Some(&index) = world
                .resources
                .mesh_cache
                .registry
                .name_to_index
                .get(&render_mesh.name)
            {
                world.resources.mesh_cache.registry.add_reference(index);
            }
            world.set_render_mesh(entity, render_mesh.clone());
            world.set_casts_shadow(entity, crate::ecs::shadow::components::CastsShadow);
        }
    }

    if let Some(material) = &node.components.material {
        if let Some(base_texture) = &material.base_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, base_texture);
        }
        if let Some(emissive_texture) = &material.emissive_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, emissive_texture);
        }

        let node_name = node
            .components
            .name
            .as_ref()
            .map(|n| n.0.as_str())
            .unwrap_or("unnamed");
        let material_name = format!("Prefab_{}_{}", node_name, entity.id);
        material_registry_insert(
            &mut world.resources.material_registry,
            material_name.clone(),
            material.clone(),
        );
        if let Some(&index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get(&material_name)
        {
            world
                .resources
                .material_registry
                .registry
                .add_reference(index);
        }
        world.set_material_ref(entity, MaterialRef::new(material_name));
    }

    if let Some(camera) = &node.components.camera {
        world.set_camera(entity, *camera);
    }

    if let Some(visibility) = &node.components.visibility {
        world.set_visibility(entity, visibility.clone());
    }

    if let Some(bounding_volume) = &node.components.bounding_volume {
        world.set_bounding_volume(entity, *bounding_volume);
    } else if let Some(calculated_bv) = calculated_bounding_volume {
        world.set_bounding_volume(entity, calculated_bv);
    }

    #[cfg(feature = "physics")]
    if let Some(prefab_rigid_body) = &node.components.rigid_body {
        let mut rigid_body_component = prefab_rigid_body.to_component();
        rigid_body_component.translation = local_transform.translation.into();
        let quat = local_transform.rotation;
        rigid_body_component.rotation = [quat.i, quat.j, quat.k, quat.w];

        let rapier_rigid_body = rigid_body_component.to_rapier_rigid_body();
        let rapier_handle = world.resources.physics.add_rigid_body(rapier_rigid_body);
        rigid_body_component.handle = Some(rapier_handle.into());

        if let Some(prefab_collider) = &node.components.collider {
            let mut collider_component = prefab_collider.to_component();
            let rapier_collider = collider_component.to_rapier_collider();
            let rapier_collider_handle = world
                .resources
                .physics
                .add_collider(rapier_collider, rapier_handle);
            collider_component.handle = Some(rapier_collider_handle.into());
            world.set_collider(entity, collider_component);
        }

        world.set_rigid_body(entity, rigid_body_component);
    }

    if let Some(parent) = parent_entity {
        if world.get_local_transform(parent).is_some() {
            world.set_parent(entity, Parent(Some(parent)));

            world
                .resources
                .children_cache
                .entry(parent)
                .or_default()
                .push(entity);
        } else {
            tracing::warn!(
                "Parent entity {:?} does not exist or lacks LocalTransform component when spawning entity {:?}",
                parent,
                entity
            );
        }
    }

    for child_node in &node.children {
        spawn_prefab_node(world, child_node, offset, Some(entity));
    }

    entity
}

pub fn spawn_prefab_with_animations(
    world: &mut World,
    prefab: &Prefab,
    animations: &[AnimationClip],
    offset: Vec3,
) -> Entity {
    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
        1,
    )[0];

    world.set_name(
        parent_entity,
        crate::ecs::world::components::Name(prefab.name.clone()),
    );
    world.set_local_transform(
        parent_entity,
        crate::ecs::world::components::LocalTransform {
            translation: offset,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::vec3(1.0, 1.0, 1.0),
        },
    );
    world.set_global_transform(parent_entity, GlobalTransform::default());
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);

    let mut node_index_to_entity: HashMap<usize, Entity> = HashMap::new();
    let mut bone_name_to_entity: HashMap<String, Entity> = HashMap::new();

    for root_node in &prefab.root_nodes {
        spawn_prefab_node_with_mapping(
            world,
            root_node,
            Vec3::zeros(),
            Some(parent_entity),
            &mut node_index_to_entity,
            &mut bone_name_to_entity,
        );
    }

    if !animations.is_empty() {
        let mut animation_player = AnimationPlayer {
            clips: animations.to_vec(),
            current_clip: if animations.is_empty() { None } else { Some(0) },
            time: 0.0,
            speed: 1.0,
            looping: true,
            playing: true,
            node_index_to_entity,
            bone_name_to_entity,
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        };

        if !animation_player.clips.is_empty() {
            animation_player.play(0);
        }

        world.add_components(parent_entity, ANIMATION_PLAYER);
        world.set_animation_player(parent_entity, animation_player);
    }

    parent_entity
}

fn spawn_prefab_node_with_mapping(
    world: &mut World,
    node: &PrefabNode,
    offset: Vec3,
    parent_entity: Option<Entity>,
    node_index_to_entity: &mut HashMap<usize, Entity>,
    bone_name_to_entity: &mut HashMap<String, Entity>,
) -> Entity {
    let calculated_bounding_volume = if node.components.bounding_volume.is_none() {
        if let Some(render_mesh) = &node.components.render_mesh {
            get_bounding_volume_from_mesh_name(world, &render_mesh.name)
        } else {
            None
        }
    } else {
        None
    };

    let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;

    if node.components.name.is_some() {
        component_mask |= NAME;
    }

    if node.components.render_mesh.is_some() {
        component_mask |= RENDER_MESH | CASTS_SHADOW;
    }

    if node.components.material.is_some() {
        component_mask |= MATERIAL_REF;
    }

    if node.components.camera.is_some() {
        component_mask |= CAMERA;
    }

    if node.components.visibility.is_some() {
        component_mask |= VISIBILITY;
    }

    if node.components.bounding_volume.is_some() || calculated_bounding_volume.is_some() {
        component_mask |= BOUNDING_VOLUME;
    }

    if node.components.rigid_body.is_some() {
        component_mask |= RIGID_BODY;
    }

    if node.components.collider.is_some() {
        component_mask |= COLLIDER;
    }

    if node.components.script.is_some() {
        component_mask |= SCRIPT;
    }

    if parent_entity.is_some() {
        component_mask |= PARENT;
    }

    let entity = world.spawn_entities(component_mask, 1)[0];

    if let Some(node_index) = node.node_index {
        node_index_to_entity.insert(node_index, entity);
    }

    if let Some(name) = &node.components.name {
        bone_name_to_entity.insert(name.0.clone(), entity);
    }

    let mut local_transform = node.local_transform;
    if parent_entity.is_none() {
        local_transform.translation += offset;
    }

    world.set_local_transform(entity, local_transform);
    world.set_global_transform(entity, GlobalTransform::default());
    world.set_local_transform_dirty(entity, LocalTransformDirty);

    if let Some(name) = &node.components.name {
        world.set_name(entity, name.clone());
    }

    if let Some(script) = &node.components.script {
        world.set_script(entity, script.clone());
    }

    if let Some(render_mesh) = &node.components.render_mesh {
        if !world
            .resources
            .mesh_cache
            .registry
            .name_to_index
            .contains_key(&render_mesh.name)
        {
            tracing::error!(
                "Mesh '{}' not found in mesh_cache when spawning entity {:?}. Skipping RenderMesh component.",
                render_mesh.name,
                entity
            );
        } else {
            if let Some(&index) = world
                .resources
                .mesh_cache
                .registry
                .name_to_index
                .get(&render_mesh.name)
            {
                world.resources.mesh_cache.registry.add_reference(index);
            }
            world.set_render_mesh(entity, render_mesh.clone());
            world.set_casts_shadow(entity, crate::ecs::shadow::components::CastsShadow);
        }
    }

    if let Some(material) = &node.components.material {
        if let Some(base_texture) = &material.base_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, base_texture);
        }
        if let Some(emissive_texture) = &material.emissive_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, emissive_texture);
        }

        let node_name = node
            .components
            .name
            .as_ref()
            .map(|n| n.0.as_str())
            .unwrap_or("unnamed");
        let material_name = format!("Prefab_{}_{}", node_name, entity.id);
        material_registry_insert(
            &mut world.resources.material_registry,
            material_name.clone(),
            material.clone(),
        );
        if let Some(&index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get(&material_name)
        {
            world
                .resources
                .material_registry
                .registry
                .add_reference(index);
        }
        world.set_material_ref(entity, MaterialRef::new(material_name));
    }

    if let Some(camera) = &node.components.camera {
        world.set_camera(entity, *camera);
    }

    if let Some(visibility) = &node.components.visibility {
        world.set_visibility(entity, visibility.clone());
    }

    if let Some(bounding_volume) = &node.components.bounding_volume {
        world.set_bounding_volume(entity, *bounding_volume);
    } else if let Some(calculated_bv) = calculated_bounding_volume {
        world.set_bounding_volume(entity, calculated_bv);
    }

    #[cfg(feature = "physics")]
    if let Some(prefab_rigid_body) = &node.components.rigid_body {
        let mut rigid_body_component = prefab_rigid_body.to_component();
        rigid_body_component.translation = local_transform.translation.into();
        let quat = local_transform.rotation;
        rigid_body_component.rotation = [quat.i, quat.j, quat.k, quat.w];

        let rapier_rigid_body = rigid_body_component.to_rapier_rigid_body();
        let rapier_handle = world.resources.physics.add_rigid_body(rapier_rigid_body);
        rigid_body_component.handle = Some(rapier_handle.into());

        if let Some(prefab_collider) = &node.components.collider {
            let mut collider_component = prefab_collider.to_component();
            let rapier_collider = collider_component.to_rapier_collider();
            let rapier_collider_handle = world
                .resources
                .physics
                .add_collider(rapier_collider, rapier_handle);
            collider_component.handle = Some(rapier_collider_handle.into());
            world.set_collider(entity, collider_component);
        }

        world.set_rigid_body(entity, rigid_body_component);
    }

    if let Some(parent) = parent_entity {
        if world.get_local_transform(parent).is_some() {
            world.set_parent(entity, Parent(Some(parent)));

            world
                .resources
                .children_cache
                .entry(parent)
                .or_default()
                .push(entity);
        } else {
            tracing::warn!(
                "Parent entity {:?} does not exist or lacks LocalTransform component when spawning entity {:?}",
                parent,
                entity
            );
        }
    }

    for child_node in &node.children {
        spawn_prefab_node_with_mapping(
            world,
            child_node,
            offset,
            Some(entity),
            node_index_to_entity,
            bone_name_to_entity,
        );
    }

    entity
}

pub fn spawn_prefab_with_source(
    world: &mut World,
    prefab: &Prefab,
    animations: &[AnimationClip],
    skins: &[GltfSkin],
    offset: Vec3,
    source_path: Option<&str>,
) -> Entity {
    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | PREFAB_SOURCE,
        1,
    )[0];

    world.set_name(
        parent_entity,
        crate::ecs::world::components::Name(prefab.name.clone()),
    );
    world.set_local_transform(
        parent_entity,
        crate::ecs::world::components::LocalTransform {
            translation: offset,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::vec3(1.0, 1.0, 1.0),
        },
    );
    world.set_global_transform(parent_entity, GlobalTransform::default());
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);

    let prefab_source = if let Some(path) = source_path {
        PrefabSource::with_path(&prefab.name, path)
    } else {
        PrefabSource::new(&prefab.name)
    };
    world.set_prefab_source(parent_entity, prefab_source);

    let mut node_index_to_entity: HashMap<usize, Entity> = HashMap::new();
    let mut bone_name_to_entity: HashMap<String, Entity> = HashMap::new();
    let mut entity_to_skin_index: HashMap<Entity, usize> = HashMap::new();

    for root_node in &prefab.root_nodes {
        spawn_prefab_node_with_skin_mapping(
            world,
            root_node,
            Vec3::zeros(),
            Some(parent_entity),
            &mut node_index_to_entity,
            &mut bone_name_to_entity,
            &mut entity_to_skin_index,
        );
    }

    tracing::info!(
        "spawn_prefab_with_source: {} node mappings, {} bone names, {} entities need skins",
        node_index_to_entity.len(),
        bone_name_to_entity.len(),
        entity_to_skin_index.len()
    );

    for (&entity, &skin_index) in &entity_to_skin_index {
        if skin_index < skins.len() {
            let gltf_skin = &skins[skin_index];

            let joints: Vec<Entity> = gltf_skin
                .joints
                .iter()
                .filter_map(|&joint_node_index| {
                    node_index_to_entity.get(&joint_node_index).copied()
                })
                .collect();

            tracing::info!(
                "  Skin {}: expected {} joints, found {} (name: {:?})",
                skin_index,
                gltf_skin.joints.len(),
                joints.len(),
                gltf_skin.name
            );
            if joints.len() < gltf_skin.joints.len() {
                let missing: Vec<_> = gltf_skin
                    .joints
                    .iter()
                    .filter(|idx| !node_index_to_entity.contains_key(*idx))
                    .take(5)
                    .collect();
                tracing::warn!("  Missing joint node indices (first 5): {:?}", missing);
            }

            for (joint_index, &joint_entity) in joints.iter().enumerate() {
                world.add_components(joint_entity, JOINT);
                world.set_joint(
                    joint_entity,
                    Joint {
                        index_in_skin: joint_index,
                        name: None,
                    },
                );
            }

            let skin = Skin {
                joints,
                inverse_bind_matrices: Arc::new(gltf_skin.inverse_bind_matrices.clone()),
                name: gltf_skin.name.clone(),
            };

            world.add_components(entity, SKIN);
            world.set_skin(entity, skin);
        }
    }

    if !animations.is_empty() {
        tracing::info!("Setting up AnimationPlayer with {} clips", animations.len());
        for (clip_idx, clip) in animations.iter().enumerate() {
            tracing::info!(
                "  Clip {} '{}': {} channels, duration: {}s",
                clip_idx,
                clip.name,
                clip.channels.len(),
                clip.duration
            );
            for (ch_idx, channel) in clip.channels.iter().take(5).enumerate() {
                let found_by_name = channel
                    .target_bone_name
                    .as_ref()
                    .and_then(|name| bone_name_to_entity.get(name));
                let found_by_idx = node_index_to_entity.get(&channel.target_node);
                tracing::info!(
                    "    Ch{}: node={}, bone={:?}, found_name={}, found_idx={}",
                    ch_idx,
                    channel.target_node,
                    channel.target_bone_name,
                    found_by_name.is_some(),
                    found_by_idx.is_some()
                );
            }
            if clip.channels.len() > 5 {
                tracing::info!("    ... and {} more channels", clip.channels.len() - 5);
            }
        }
        let mut animation_player = AnimationPlayer {
            clips: animations.to_vec(),
            current_clip: if animations.is_empty() { None } else { Some(0) },
            time: 0.0,
            speed: 1.0,
            looping: true,
            playing: true,
            node_index_to_entity,
            bone_name_to_entity,
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        };

        if !animation_player.clips.is_empty() {
            animation_player.play(0);
        }

        world.add_components(parent_entity, ANIMATION_PLAYER);
        world.set_animation_player(parent_entity, animation_player);
    }

    parent_entity
}

pub fn spawn_prefab_with_skins(
    world: &mut World,
    prefab: &Prefab,
    animations: &[AnimationClip],
    skins: &[GltfSkin],
    offset: Vec3,
) -> Entity {
    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
        1,
    )[0];

    world.set_name(
        parent_entity,
        crate::ecs::world::components::Name(prefab.name.clone()),
    );
    world.set_local_transform(
        parent_entity,
        crate::ecs::world::components::LocalTransform {
            translation: offset,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::vec3(1.0, 1.0, 1.0),
        },
    );
    world.set_global_transform(parent_entity, GlobalTransform::default());
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);

    let mut node_index_to_entity: HashMap<usize, Entity> = HashMap::new();
    let mut bone_name_to_entity: HashMap<String, Entity> = HashMap::new();
    let mut entity_to_skin_index: HashMap<Entity, usize> = HashMap::new();

    for root_node in &prefab.root_nodes {
        spawn_prefab_node_with_skin_mapping(
            world,
            root_node,
            Vec3::zeros(),
            Some(parent_entity),
            &mut node_index_to_entity,
            &mut bone_name_to_entity,
            &mut entity_to_skin_index,
        );
    }

    for (&entity, &skin_index) in &entity_to_skin_index {
        if skin_index < skins.len() {
            let gltf_skin = &skins[skin_index];

            let joints: Vec<Entity> = gltf_skin
                .joints
                .iter()
                .filter_map(|&joint_node_index| {
                    node_index_to_entity.get(&joint_node_index).copied()
                })
                .collect();

            for (joint_index, &joint_entity) in joints.iter().enumerate() {
                world.add_components(joint_entity, JOINT);
                world.set_joint(
                    joint_entity,
                    Joint {
                        index_in_skin: joint_index,
                        name: None,
                    },
                );
            }

            let skin = Skin {
                joints,
                inverse_bind_matrices: Arc::new(gltf_skin.inverse_bind_matrices.clone()),
                name: gltf_skin.name.clone(),
            };

            world.add_components(entity, SKIN);
            world.set_skin(entity, skin);
        }
    }

    if !animations.is_empty() {
        let mut animation_player = AnimationPlayer {
            clips: animations.to_vec(),
            current_clip: if animations.is_empty() { None } else { Some(0) },
            time: 0.0,
            speed: 1.0,
            looping: true,
            playing: true,
            node_index_to_entity,
            bone_name_to_entity,
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        };

        if !animation_player.clips.is_empty() {
            animation_player.play(0);
        }

        world.add_components(parent_entity, ANIMATION_PLAYER);
        world.set_animation_player(parent_entity, animation_player);
    }

    parent_entity
}

fn spawn_prefab_node_with_skin_mapping(
    world: &mut World,
    node: &PrefabNode,
    offset: Vec3,
    parent_entity: Option<Entity>,
    node_index_to_entity: &mut HashMap<usize, Entity>,
    bone_name_to_entity: &mut HashMap<String, Entity>,
    entity_to_skin_index: &mut HashMap<Entity, usize>,
) -> Entity {
    let calculated_bounding_volume = if node.components.bounding_volume.is_none() {
        if let Some(render_mesh) = &node.components.render_mesh {
            get_bounding_volume_from_mesh_name(world, &render_mesh.name)
        } else {
            None
        }
    } else {
        None
    };

    let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;

    if node.components.name.is_some() {
        component_mask |= NAME;
    }

    if node.components.render_mesh.is_some() {
        component_mask |= RENDER_MESH | CASTS_SHADOW;
    }

    if node.components.material.is_some() {
        component_mask |= MATERIAL_REF;
    }

    if node.components.camera.is_some() {
        component_mask |= CAMERA;
    }

    if node.components.visibility.is_some() {
        component_mask |= VISIBILITY;
    }

    if node.components.bounding_volume.is_some() || calculated_bounding_volume.is_some() {
        component_mask |= BOUNDING_VOLUME;
    }

    if node.components.rigid_body.is_some() {
        component_mask |= RIGID_BODY;
    }

    if node.components.collider.is_some() {
        component_mask |= COLLIDER;
    }

    if node.components.script.is_some() {
        component_mask |= SCRIPT;
    }

    if parent_entity.is_some() {
        component_mask |= PARENT;
    }

    let entity = world.spawn_entities(component_mask, 1)[0];

    if let Some(node_index) = node.node_index {
        node_index_to_entity.insert(node_index, entity);
    }

    if let Some(name) = &node.components.name {
        bone_name_to_entity.insert(name.0.clone(), entity);
    }

    if let Some(skin_index) = node.components.skin_index {
        entity_to_skin_index.insert(entity, skin_index);
    }

    let mut local_transform = node.local_transform;
    if parent_entity.is_none() {
        local_transform.translation += offset;
    }

    world.set_local_transform(entity, local_transform);
    world.set_global_transform(entity, GlobalTransform::default());
    world.set_local_transform_dirty(entity, LocalTransformDirty);

    if let Some(name) = &node.components.name {
        world.set_name(entity, name.clone());
    }

    if let Some(script) = &node.components.script {
        world.set_script(entity, script.clone());
    }

    if let Some(render_mesh) = &node.components.render_mesh {
        if !world
            .resources
            .mesh_cache
            .registry
            .name_to_index
            .contains_key(&render_mesh.name)
        {
            tracing::error!(
                "Mesh '{}' not found in mesh_cache when spawning entity {:?}. Skipping RenderMesh component.",
                render_mesh.name,
                entity
            );
        } else {
            if let Some(&index) = world
                .resources
                .mesh_cache
                .registry
                .name_to_index
                .get(&render_mesh.name)
            {
                world.resources.mesh_cache.registry.add_reference(index);
            }
            world.set_render_mesh(entity, render_mesh.clone());
            world.set_casts_shadow(entity, crate::ecs::shadow::components::CastsShadow);

            let morph_weights_to_add =
                registry_entry_by_name(&world.resources.mesh_cache.registry, &render_mesh.name)
                    .and_then(|mesh| mesh.morph_targets.as_ref())
                    .map(|morph_data| {
                        MorphWeights::new(
                            morph_data.default_weights.clone(),
                            render_mesh.name.clone(),
                        )
                    });

            if let Some(morph_weights) = morph_weights_to_add {
                world.add_components(entity, MORPH_WEIGHTS);
                world.set_morph_weights(entity, morph_weights);
            }
        }
    }

    if let Some(material) = &node.components.material {
        if let Some(base_texture) = &material.base_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, base_texture);
        }
        if let Some(emissive_texture) = &material.emissive_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, emissive_texture);
        }

        let node_name = node
            .components
            .name
            .as_ref()
            .map(|n| n.0.as_str())
            .unwrap_or("unnamed");
        let material_name = format!("Prefab_{}_{}", node_name, entity.id);
        material_registry_insert(
            &mut world.resources.material_registry,
            material_name.clone(),
            material.clone(),
        );
        if let Some(&index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get(&material_name)
        {
            world
                .resources
                .material_registry
                .registry
                .add_reference(index);
        }
        world.set_material_ref(entity, MaterialRef::new(material_name));
    }

    if let Some(camera) = &node.components.camera {
        world.set_camera(entity, *camera);
    }

    if let Some(visibility) = &node.components.visibility {
        world.set_visibility(entity, visibility.clone());
    }

    if let Some(bounding_volume) = &node.components.bounding_volume {
        world.set_bounding_volume(entity, *bounding_volume);
    } else if let Some(calculated_bv) = calculated_bounding_volume {
        world.set_bounding_volume(entity, calculated_bv);
    }

    #[cfg(feature = "physics")]
    if let Some(prefab_rigid_body) = &node.components.rigid_body {
        let mut rigid_body_component = prefab_rigid_body.to_component();
        rigid_body_component.translation = local_transform.translation.into();
        let quat = local_transform.rotation;
        rigid_body_component.rotation = [quat.i, quat.j, quat.k, quat.w];

        let rapier_rigid_body = rigid_body_component.to_rapier_rigid_body();
        let rapier_handle = world.resources.physics.add_rigid_body(rapier_rigid_body);
        rigid_body_component.handle = Some(rapier_handle.into());

        if let Some(prefab_collider) = &node.components.collider {
            let mut collider_component = prefab_collider.to_component();
            let rapier_collider = collider_component.to_rapier_collider();
            let rapier_collider_handle = world
                .resources
                .physics
                .add_collider(rapier_collider, rapier_handle);
            collider_component.handle = Some(rapier_collider_handle.into());
            world.set_collider(entity, collider_component);
        }

        world.set_rigid_body(entity, rigid_body_component);
    }

    if let Some(parent) = parent_entity {
        if world.get_local_transform(parent).is_some() {
            world.set_parent(entity, Parent(Some(parent)));

            world
                .resources
                .children_cache
                .entry(parent)
                .or_default()
                .push(entity);
        } else {
            tracing::warn!(
                "Parent entity {:?} does not exist or lacks LocalTransform component when spawning entity {:?}",
                parent,
                entity
            );
        }
    }

    for child_node in &node.children {
        spawn_prefab_node_with_skin_mapping(
            world,
            child_node,
            offset,
            Some(entity),
            node_index_to_entity,
            bone_name_to_entity,
            entity_to_skin_index,
        );
    }

    entity
}
