use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::world::components::{GlobalTransform, LocalTransformDirty, Parent};
use crate::ecs::world::{
    BOUNDING_VOLUME, CAMERA, CASTS_SHADOW, GLOBAL_TRANSFORM, LOCAL_TRANSFORM,
    LOCAL_TRANSFORM_DIRTY, MATERIAL, NAME, PARENT, RENDER_MESH, VISIBILITY, Vec3, World,
};
use freecs::Entity;

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

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

pub fn spawn_prefab(world: &mut World, prefab: &Prefab, offset: Vec3) -> Vec<Entity> {
    let mut root_entities = Vec::new();

    for root_node in &prefab.root_nodes {
        let entity = spawn_prefab_node(world, root_node, offset, None);
        root_entities.push(entity);
    }

    root_entities
}

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;
    }

    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 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(render_mesh) = &node.components.render_mesh {
        if !world.resources.mesh_cache.contains(&render_mesh.name) {
            log::error!(
                "Mesh '{}' not found in mesh_cache when spawning entity {:?}. Skipping RenderMesh component.",
                render_mesh.name,
                entity
            );
        } else {
            world.resources.mesh_cache.add_reference(&render_mesh.name);
            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 {
        let mut material_to_set = material.clone();

        if let Some(base_texture) = &material.base_texture {
            world.resources.texture_cache.add_reference(base_texture);
        }
        if let Some(emissive_texture) = &material.emissive_texture {
            world
                .resources
                .texture_cache
                .add_reference(emissive_texture);
        }

        let material_id = world
            .resources
            .material_cache
            .insert_or_get_id(&material_to_set);
        world.resources.material_cache.add_reference(&material_id);
        material_to_set.cache_id = Some(material_id);

        world.set_material(entity, material_to_set);
    }

    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);
    }

    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 {
            log::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
}
