use crate::ecs::generational_registry::{registry_entry_by_name, registry_entry_by_name_mut};
use crate::ecs::material::components::MaterialRef;
use crate::ecs::material::resources::{
    material_registry_insert, material_registry_lookup_id, material_registry_remove_unused,
};
use crate::ecs::mesh::components::{
    create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
    create_sphere_mesh, create_subdivided_plane_mesh, create_torus_mesh,
};
use crate::ecs::prefab::resources::{mesh_cache_insert, mesh_cache_lookup_id};
use crate::ecs::world::{Entity, NAME, World};
use crate::render::wgpu::texture_cache::{
    texture_cache_add_reference, texture_cache_remove_reference, texture_cache_remove_unused,
};

pub type CommandQueue = Vec<WorldCommand>;

#[derive(Debug, Clone)]
pub enum WorldCommand {
    DespawnRecursive {
        entity: Entity,
    },
    UploadSpriteTexture {
        slot: u32,
        rgba_data: Vec<u8>,
        width: u32,
        height: u32,
    },
    LoadTexture {
        name: String,
        rgba_data: Vec<u8>,
        width: u32,
        height: u32,
    },
    LoadHdrSkybox {
        hdr_data: Vec<u8>,
    },
    LoadHdrSkyboxFromPath {
        path: std::path::PathBuf,
    },
}

impl World {
    pub fn queue_command(&mut self, command: WorldCommand) {
        self.resources.command_queue.push(command);
    }
}

pub fn process_commands_system(world: &mut World) {
    let commands = world.resources.command_queue.drain(..).collect::<Vec<_>>();
    let _span = tracing::info_span!("commands", count = commands.len()).entered();
    let mut sprite_texture_commands = Vec::new();

    for command in commands {
        match &command {
            WorldCommand::UploadSpriteTexture { .. }
            | WorldCommand::LoadTexture { .. }
            | WorldCommand::LoadHdrSkybox { .. }
            | WorldCommand::LoadHdrSkyboxFromPath { .. } => {
                sprite_texture_commands.push(command);
            }
            _ => {
                process_command(world, command);
            }
        }
    }

    world
        .resources
        .command_queue
        .extend(sprite_texture_commands);
}

fn process_command(world: &mut World, command: WorldCommand) {
    match command {
        WorldCommand::DespawnRecursive { entity } => {
            if !world.resources.children_cache_valid {
                world.validate_and_rebuild_children_cache();
            }
            let descendents = query_descendents(world, entity);
            despawn_entities_with_cache_cleanup(world, &descendents);
        }
        WorldCommand::UploadSpriteTexture { .. } => {}
        WorldCommand::LoadTexture { .. } => {}
        WorldCommand::LoadHdrSkybox { .. } => {}
        WorldCommand::LoadHdrSkyboxFromPath { .. } => {}
    }
}

fn query_children(world: &World, entity: Entity) -> Vec<Entity> {
    world
        .resources
        .children_cache
        .get(&entity)
        .cloned()
        .unwrap_or_default()
}

fn query_descendents(world: &World, target_entity: Entity) -> Vec<Entity> {
    let mut descendents = Vec::new();
    let mut stack = vec![target_entity];
    while let Some(entity) = stack.pop() {
        descendents.push(entity);
        for child in query_children(world, entity) {
            stack.push(child);
        }
    }
    descendents
}

fn despawn_entities_with_cache_cleanup(world: &mut World, entities: &[Entity]) {
    let mut has_parent_entities = false;

    for &entity in entities {
        world.resources.children_cache.remove(&entity);

        if let Some(parent_component) = world.get_parent(entity)
            && let Some(parent_entity) = parent_component.0
            && let Some(children) = world.resources.children_cache.get_mut(&parent_entity)
        {
            children.retain(|&e| e != entity);
            has_parent_entities = true;
        }

        if let Some(render_mesh) = world.get_render_mesh(entity) {
            let mesh_name = render_mesh.name.clone();
            if let Some(&index) = world
                .resources
                .mesh_cache
                .registry
                .name_to_index
                .get(&mesh_name)
            {
                world.resources.mesh_cache.registry.remove_reference(index);
            }
        }

        if let Some(material_ref) = world.get_material_ref(entity) {
            let material_name = material_ref.name.clone();
            if let Some(material) =
                registry_entry_by_name(&world.resources.material_registry.registry, &material_name)
            {
                if let Some(base_texture) = &material.base_texture {
                    texture_cache_remove_reference(
                        &mut world.resources.texture_cache,
                        base_texture,
                    );
                }
                if let Some(emissive_texture) = &material.emissive_texture {
                    texture_cache_remove_reference(
                        &mut world.resources.texture_cache,
                        emissive_texture,
                    );
                }
            }
            if let Some(&index) = world
                .resources
                .material_registry
                .registry
                .name_to_index
                .get(&material_name)
            {
                world
                    .resources
                    .material_registry
                    .registry
                    .remove_reference(index);
            }
        }
    }

    if has_parent_entities {
        world.resources.children_cache_valid = false;
    }

    world.despawn_entities(entities);
}

pub fn despawn_recursive_immediate(world: &mut World, entity: Entity) {
    if !world.resources.children_cache_valid {
        world.validate_and_rebuild_children_cache();
    }
    let descendents = query_descendents(world, entity);
    despawn_entities_with_cache_cleanup(world, &descendents);
}

pub fn spawn_sun(world: &mut World) -> Entity {
    use crate::ecs::world::components;
    use crate::ecs::world::{
        GLOBAL_TRANSFORM, LIGHT, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, NAME,
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | LIGHT,
        1,
    )[0];

    world.set_name(entity, components::Name("Sun".to_string()));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: nalgebra_glm::Vec3::new(5.0, 10.0, 5.0),
            rotation: nalgebra_glm::quat_angle_axis(
                std::f32::consts::FRAC_PI_4,
                &nalgebra_glm::Vec3::new(0.0, 1.0, 0.0),
            ) * nalgebra_glm::quat_angle_axis(
                -std::f32::consts::FRAC_PI_6,
                &nalgebra_glm::Vec3::new(1.0, 0.0, 0.0),
            ),
            scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_light(
        entity,
        components::Light {
            light_type: components::LightType::Directional,
            color: nalgebra_glm::Vec3::new(1.0, 0.95, 0.8),
            intensity: 5.0,
            range: 100.0,
            inner_cone_angle: std::f32::consts::PI / 6.0,
            outer_cone_angle: std::f32::consts::PI / 4.0,
            cast_shadows: true,
            shadow_bias: 0.0005,
        },
    );

    entity
}

pub fn spawn_sun_without_shadows(world: &mut World) -> Entity {
    use crate::ecs::world::components;
    use crate::ecs::world::{
        GLOBAL_TRANSFORM, LIGHT, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, NAME,
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | LIGHT,
        1,
    )[0];

    world.set_name(entity, components::Name("Sun".to_string()));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: nalgebra_glm::Vec3::new(5.0, 10.0, 5.0),
            rotation: nalgebra_glm::quat_angle_axis(
                -std::f32::consts::FRAC_PI_4,
                &nalgebra_glm::Vec3::new(1.0, 0.0, 0.0),
            ),
            scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_light(
        entity,
        components::Light {
            light_type: components::LightType::Directional,
            color: nalgebra_glm::Vec3::new(1.0, 0.95, 0.8),
            intensity: 5.0,
            range: 100.0,
            inner_cone_angle: std::f32::consts::PI / 6.0,
            outer_cone_angle: std::f32::consts::PI / 4.0,
            cast_shadows: false,
            shadow_bias: 0.0,
        },
    );

    entity
}

pub fn spawn_mesh_at(
    world: &mut World,
    mesh_name: &str,
    position: nalgebra_glm::Vec3,
    scale: nalgebra_glm::Vec3,
) -> Entity {
    use crate::ecs::world::components;
    use crate::ecs::world::{
        BOUNDING_VOLUME, CASTS_SHADOW, GLOBAL_TRANSFORM, LIGHT, LOCAL_TRANSFORM,
        LOCAL_TRANSFORM_DIRTY, MATERIAL_REF, NAME, RENDER_MESH, VISIBILITY, WATER,
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | RENDER_MESH
            | MATERIAL_REF
            | BOUNDING_VOLUME
            | CASTS_SHADOW
            | VISIBILITY
            | LIGHT
            | WATER,
        1,
    )[0];

    world.set_name(entity, components::Name(mesh_name.to_string()));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: position,
            scale,
            rotation: nalgebra_glm::Quat::identity(),
        },
    );
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);
    let render_mesh = components::RenderMesh::new(mesh_name);

    if !world
        .resources
        .mesh_cache
        .registry
        .name_to_index
        .contains_key(&render_mesh.name)
    {
        let mesh = match mesh_name {
            "Cube" => Some(create_cube_mesh()),
            "Sphere" => Some(create_sphere_mesh(1.0, 16)),
            "Plane" => Some(create_plane_mesh(2.0)),
            "SubdividedPlane" => Some(create_subdivided_plane_mesh(2.0, 20)),
            "Torus" => Some(create_torus_mesh(1.0, 0.3, 32, 16)),
            "Cylinder" => Some(create_cylinder_mesh(0.5, 1.0, 16)),
            "Cone" => Some(create_cone_mesh(0.5, 1.0, 16)),
            _ => None,
        };
        if let Some(mesh) = mesh {
            mesh_cache_insert(&mut world.resources.mesh_cache, mesh_name.to_string(), mesh);
        }
    }

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

    if let Some(&index) = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get("Default")
    {
        world
            .resources
            .material_registry
            .registry
            .add_reference(index);
    }
    world.set_material_ref(entity, components::MaterialRef::new("Default"));

    world.set_bounding_volume(
        entity,
        components::BoundingVolume::from_mesh_type(mesh_name),
    );
    world.set_casts_shadow(entity, components::CastsShadow);

    if let Some(visibility) = world.get_visibility_mut(entity) {
        visibility.visible = true;
    }

    entity
}

pub fn spawn_cube_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    spawn_mesh_at(
        world,
        "Cube",
        position,
        nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
    )
}

pub fn spawn_sphere_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    spawn_mesh_at(
        world,
        "Sphere",
        position,
        nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
    )
}

pub fn spawn_cylinder_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    spawn_mesh_at(
        world,
        "Cylinder",
        position,
        nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
    )
}

pub fn spawn_torus_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    spawn_mesh_at(
        world,
        "Torus",
        position,
        nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
    )
}

pub fn spawn_cone_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    spawn_mesh_at(
        world,
        "Cone",
        position,
        nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
    )
}

pub fn spawn_plane_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    spawn_mesh_at(
        world,
        "Plane",
        position,
        nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
    )
}

pub fn spawn_water_plane_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
    use crate::ecs::mesh::components::create_subdivided_plane_mesh;
    use crate::ecs::water::Water;
    use crate::ecs::world::components;
    use crate::ecs::world::{
        GLOBAL_TRANSFORM, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, NAME, RENDER_MESH, VISIBILITY,
        WATER,
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | RENDER_MESH
            | VISIBILITY
            | WATER,
        1,
    )[0];

    world.set_name(entity, components::Name("Water Plane".to_string()));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: position,
            scale: nalgebra_glm::Vec3::new(10.0, 1.0, 10.0),
            rotation: nalgebra_glm::Quat::identity(),
        },
    );
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);

    let mesh_name = "WaterPlane";
    if !world
        .resources
        .mesh_cache
        .registry
        .name_to_index
        .contains_key(mesh_name)
    {
        let mesh = create_subdivided_plane_mesh(20.0, 1);
        mesh_cache_insert(&mut world.resources.mesh_cache, mesh_name.to_string(), mesh);
    }

    if let Some(&index) = world
        .resources
        .mesh_cache
        .registry
        .name_to_index
        .get(mesh_name)
    {
        world.resources.mesh_cache.registry.add_reference(index);
    }

    world.set_render_mesh(entity, components::RenderMesh::new(mesh_name));

    world.set_water(entity, Water::default());

    if let Some(visibility) = world.get_visibility_mut(entity) {
        visibility.visible = true;
    }

    entity
}

pub fn spawn_3d_text_with_properties(
    world: &mut World,
    text: &str,
    position: nalgebra_glm::Vec3,
    properties: crate::ecs::text::components::TextProperties,
) -> Entity {
    spawn_3d_text_impl(world, text, position, properties, false)
}

pub fn spawn_3d_billboard_text_with_properties(
    world: &mut World,
    text: &str,
    position: nalgebra_glm::Vec3,
    properties: crate::ecs::text::components::TextProperties,
) -> Entity {
    spawn_3d_text_impl(world, text, position, properties, true)
}

fn spawn_3d_text_impl(
    world: &mut World,
    text: &str,
    position: nalgebra_glm::Vec3,
    properties: crate::ecs::text::components::TextProperties,
    billboard: bool,
) -> Entity {
    use crate::ecs::world::components;
    use crate::ecs::world::{
        GLOBAL_TRANSFORM, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, NAME, TEXT, VISIBILITY,
    };

    let text_index = world.resources.text_cache.add_text(text);

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | TEXT | VISIBILITY,
        1,
    )[0];

    world.set_name(entity, components::Name(text.to_string()));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: position,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_text(
        entity,
        components::Text {
            text_index,
            properties,
            font_index: 0,
            dirty: true,
            cached_mesh: None,
            billboard,
        },
    );
    world.set_visibility(entity, components::Visibility { visible: true });

    entity
}

const CLEANUP_INTERVAL_FRAMES: u64 = 300;

pub fn cleanup_unused_resources_system(world: &mut World) {
    let _span = tracing::info_span!("cleanup").entered();
    world.resources.cleanup_frame_counter += 1;

    if world.resources.cleanup_frame_counter >= CLEANUP_INTERVAL_FRAMES {
        world.resources.cleanup_frame_counter = 0;

        let removed_meshes = world.resources.mesh_cache.registry.remove_unused();
        if !removed_meshes.is_empty() {
            tracing::debug!("Cleaned up {} unused meshes", removed_meshes.len());
        }

        let removed_materials =
            material_registry_remove_unused(&mut world.resources.material_registry);
        if !removed_materials.is_empty() {
            tracing::debug!("Cleaned up {} unused materials", removed_materials.len());
        }

        let removed_textures = texture_cache_remove_unused(&mut world.resources.texture_cache);
        if !removed_textures.is_empty() {
            tracing::debug!("Cleaned up {} unused textures", removed_textures.len());
        }
    }
}

pub fn set_material_with_textures(
    world: &mut World,
    entity: Entity,
    material: crate::ecs::material::components::Material,
) {
    let material_ref = world.get_material_ref(entity).cloned();

    if let Some(ref mat_ref) = material_ref {
        let old_textures =
            registry_entry_by_name(&world.resources.material_registry.registry, &mat_ref.name)
                .map(|mat| (mat.base_texture.clone(), mat.emissive_texture.clone()));

        if let Some((old_base, old_emissive)) = old_textures {
            if let Some(base_texture) = old_base {
                texture_cache_remove_reference(&mut world.resources.texture_cache, &base_texture);
            }
            if let Some(emissive_texture) = old_emissive {
                texture_cache_remove_reference(
                    &mut world.resources.texture_cache,
                    &emissive_texture,
                );
            }
        }
    }

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

    if let Some(ref mat_ref) = material_ref {
        if let Some(existing_mat) = registry_entry_by_name_mut(
            &mut world.resources.material_registry.registry,
            &mat_ref.name,
        ) {
            *existing_mat = material;
        }
    } else {
        let material_name = format!("TexturedMaterial_{}", entity.id);
        material_registry_insert(
            &mut world.resources.material_registry,
            material_name.clone(),
            material,
        );
        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,
            crate::ecs::material::components::MaterialRef::new(material_name),
        );
    }
}

pub fn load_procedural_textures(world: &mut World) {
    let checkerboard = generate_checkerboard_texture();
    world.queue_command(WorldCommand::LoadTexture {
        name: "checkerboard".to_string(),
        rgba_data: checkerboard.0,
        width: checkerboard.1,
        height: checkerboard.2,
    });

    let gradient = generate_gradient_texture();
    world.queue_command(WorldCommand::LoadTexture {
        name: "gradient".to_string(),
        rgba_data: gradient.0,
        width: gradient.1,
        height: gradient.2,
    });

    let uv_test = generate_uv_test_texture();
    world.queue_command(WorldCommand::LoadTexture {
        name: "uv_test".to_string(),
        rgba_data: uv_test.0,
        width: uv_test.1,
        height: uv_test.2,
    });
}

fn generate_checkerboard_texture() -> (Vec<u8>, u32, u32) {
    let width = 256;
    let height = 256;
    let checker_size = 32;

    let mut pixels = Vec::new();
    for y in 0..height {
        for x in 0..width {
            let checker_x = (x / checker_size) % 2;
            let checker_y = (y / checker_size) % 2;
            let is_white = (checker_x + checker_y) % 2 == 0;

            if is_white {
                pixels.extend_from_slice(&[255, 255, 255, 255]);
            } else {
                pixels.extend_from_slice(&[64, 64, 64, 255]);
            }
        }
    }

    (pixels, width, height)
}

fn generate_gradient_texture() -> (Vec<u8>, u32, u32) {
    let width = 256;
    let height = 256;

    let mut pixels = Vec::new();
    for y in 0..height {
        for x in 0..width {
            let r = (x * 255 / width) as u8;
            let g = (y * 255 / height) as u8;
            let b = 128u8;
            pixels.extend_from_slice(&[r, g, b, 255]);
        }
    }

    (pixels, width, height)
}

fn generate_uv_test_texture() -> (Vec<u8>, u32, u32) {
    let width = 256;
    let height = 256;

    let mut pixels = Vec::new();
    for y in 0..height {
        for x in 0..width {
            let u = x as f32 / width as f32;
            let v = y as f32 / height as f32;

            let r = (u * 255.0) as u8;
            let g = (v * 255.0) as u8;
            let b = ((1.0 - u) * (1.0 - v) * 255.0) as u8;

            pixels.extend_from_slice(&[r, g, b, 255]);
        }
    }

    (pixels, width, height)
}

pub fn load_hdr_skybox(world: &mut World, hdr_data: Vec<u8>) {
    world.queue_command(WorldCommand::LoadHdrSkybox { hdr_data });
}

pub fn load_hdr_skybox_from_path(world: &mut World, path: std::path::PathBuf) {
    world.queue_command(WorldCommand::LoadHdrSkyboxFromPath { path });
}

pub fn spawn_instanced_mesh(
    world: &mut World,
    mesh_name: &str,
    instances: Vec<crate::ecs::mesh::components::InstanceTransform>,
) -> Entity {
    use crate::ecs::world::components;
    use crate::ecs::world::{
        CASTS_SHADOW, GLOBAL_TRANSFORM, INSTANCED_MESH, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY,
        MATERIAL_REF, NAME, VISIBILITY,
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | INSTANCED_MESH
            | MATERIAL_REF
            | VISIBILITY
            | CASTS_SHADOW,
        1,
    )[0];

    world.set_name(entity, components::Name(format!("Instanced {}", mesh_name)));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: nalgebra_glm::Vec3::new(0.0, 0.0, 0.0),
            scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
            rotation: nalgebra_glm::Quat::identity(),
        },
    );
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);

    world.set_instanced_mesh(
        entity,
        crate::ecs::mesh::components::InstancedMesh::with_instances(mesh_name, instances),
    );

    if let Some(&index) = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get("Default")
    {
        world
            .resources
            .material_registry
            .registry
            .add_reference(index);
    }
    world.set_material_ref(entity, components::MaterialRef::new("Default"));

    world.set_visibility(entity, components::Visibility { visible: true });
    world.set_casts_shadow(entity, components::CastsShadow);

    entity
}

pub fn spawn_instanced_mesh_with_material(
    world: &mut World,
    mesh_name: &str,
    instances: Vec<crate::ecs::mesh::components::InstanceTransform>,
    material_name: &str,
) -> Entity {
    use crate::ecs::world::components;
    use crate::ecs::world::{
        CASTS_SHADOW, GLOBAL_TRANSFORM, INSTANCED_MESH, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY,
        MATERIAL_REF, NAME, VISIBILITY,
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | INSTANCED_MESH
            | MATERIAL_REF
            | VISIBILITY
            | CASTS_SHADOW,
        1,
    )[0];

    world.set_name(entity, components::Name(format!("Instanced {}", mesh_name)));
    world.set_local_transform(
        entity,
        components::LocalTransform {
            translation: nalgebra_glm::Vec3::new(0.0, 0.0, 0.0),
            scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
            rotation: nalgebra_glm::Quat::identity(),
        },
    );
    world.set_global_transform(entity, components::GlobalTransform::default());
    world.set_local_transform_dirty(entity, components::LocalTransformDirty);

    world.set_instanced_mesh(
        entity,
        crate::ecs::mesh::components::InstancedMesh::with_instances(mesh_name, instances),
    );

    if let Some(&index) = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get(material_name)
    {
        world
            .resources
            .material_registry
            .registry
            .add_reference(index);
    }

    if let Some(mat) =
        registry_entry_by_name(&world.resources.material_registry.registry, material_name)
    {
        if let Some(base_texture) = &mat.base_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, base_texture);
        }
        if let Some(emissive_texture) = &mat.emissive_texture {
            texture_cache_add_reference(&mut world.resources.texture_cache, emissive_texture);
        }
    }

    world.set_material_ref(entity, components::MaterialRef::new(material_name));

    world.set_visibility(entity, components::Visibility { visible: true });
    world.set_casts_shadow(entity, components::CastsShadow);

    entity
}

pub fn resolve_asset_references(world: &mut World) {
    use crate::ecs::world::{MATERIAL_REF, RENDER_MESH};

    let entities_with_material_ref: Vec<Entity> = world.query_entities(MATERIAL_REF).collect();

    for entity in entities_with_material_ref {
        if let Some(material_ref) = world.get_material_ref(entity)
            && material_ref.id.is_none()
        {
            let name = material_ref.name.clone();
            if let Some(id) = material_registry_lookup_id(&world.resources.material_registry, &name)
                && let Some(material_ref_mut) = world.get_material_ref_mut(entity)
            {
                material_ref_mut.id = Some(id);
            }
        }
    }

    let entities_with_render_mesh: Vec<Entity> = world.query_entities(RENDER_MESH).collect();

    for entity in entities_with_render_mesh {
        if let Some(render_mesh) = world.get_render_mesh(entity)
            && render_mesh.id.is_none()
        {
            let name = render_mesh.name.clone();
            if let Some(id) = mesh_cache_lookup_id(&world.resources.mesh_cache, &name)
                && let Some(render_mesh_mut) = world.get_render_mesh_mut(entity)
            {
                render_mesh_mut.id = Some(id);
            }
        }
    }
}

pub fn resolve_entity_asset_references(world: &mut World, entity: Entity) {
    if let Some(material_ref) = world.get_material_ref(entity)
        && material_ref.id.is_none()
    {
        let name = material_ref.name.clone();
        if let Some(id) = material_registry_lookup_id(&world.resources.material_registry, &name)
            && let Some(material_ref_mut) = world.get_material_ref_mut(entity)
        {
            material_ref_mut.id = Some(id);
        }
    }

    if let Some(render_mesh) = world.get_render_mesh(entity)
        && render_mesh.id.is_none()
    {
        let name = render_mesh.name.clone();
        if let Some(id) = mesh_cache_lookup_id(&world.resources.mesh_cache, &name)
            && let Some(render_mesh_mut) = world.get_render_mesh_mut(entity)
        {
            render_mesh_mut.id = Some(id);
        }
    }
}

pub fn find_entity_by_name(world: &World, name: &str) -> Option<Entity> {
    world
        .query_entities(NAME)
        .find(|&entity| world.get_name(entity).is_some_and(|n| n.0 == name))
}

pub fn spawn_material(
    world: &mut World,
    entity: Entity,
    name: String,
    material: crate::ecs::material::components::Material,
) {
    material_registry_insert(
        &mut world.resources.material_registry,
        name.clone(),
        material,
    );
    if let Some(&index) = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get(&name)
    {
        world
            .resources
            .material_registry
            .registry
            .add_reference(index);
    }
    world.set_material_ref(entity, MaterialRef::new(name));
}
