use crate::ecs::generational_registry::{registry_entry, registry_entry_by_name};
use crate::ecs::skin::systems::GpuSkinData;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::collections::HashMap;

use super::super::projection::*;
use super::pass::SkinnedMeshPass;
use super::types::*;

impl PassNode<crate::ecs::world::World> for SkinnedMeshPass {
    fn name(&self) -> &str {
        "skinned_mesh_pass"
    }

    fn reads(&self) -> Vec<&str> {
        vec!["shadow_depth", "spotlight_shadow_atlas"]
    }

    fn writes(&self) -> Vec<&str> {
        vec![]
    }

    fn reads_writes(&self) -> Vec<&str> {
        vec!["color", "depth"]
    }

    fn prepare(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        world: &crate::ecs::world::World,
    ) {
        self.sync_skinned_meshes(device, queue, &world.resources.mesh_cache);

        self.skinned_entities.clear();
        let mut objects = Vec::new();
        let mut materials = Vec::new();
        let mut custom_data = Vec::new();
        let mut material_texture_map: HashMap<u32, MaterialTextures> = HashMap::new();

        let mut buffers_resized = false;
        let max_buffer_size = device.limits().max_buffer_size as usize;
        let matrix_size = std::mem::size_of::<[[f32; 4]; 4]>();

        if self.skinning_cache.needs_rebuild(world) {
            self.skinning_cache.rebuild_static_data(world);

            if !self.skinning_cache.inverse_bind_matrices.is_empty() {
                let required_size = self.skinning_cache.inverse_bind_matrices.len();
                if required_size > self.inverse_bind_matrices_buffer_size {
                    let new_size = (required_size as f32 * 2.0).ceil() as usize;
                    let buffer_bytes = matrix_size * new_size;
                    if buffer_bytes > max_buffer_size {
                        tracing::error!(
                            "Inverse bind matrices buffer would exceed GPU limit: {} > {} bytes",
                            buffer_bytes,
                            max_buffer_size
                        );
                    } else {
                        self.inverse_bind_matrices_buffer =
                            device.create_buffer(&wgpu::BufferDescriptor {
                                label: Some("Inverse Bind Matrices Buffer (Resized)"),
                                size: buffer_bytes as u64,
                                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                                mapped_at_creation: false,
                            });
                        self.inverse_bind_matrices_buffer_size = new_size;
                        buffers_resized = true;
                    }
                }
                if self.skinning_cache.inverse_bind_matrices.len()
                    <= self.inverse_bind_matrices_buffer_size
                {
                    queue.write_buffer(
                        &self.inverse_bind_matrices_buffer,
                        0,
                        bytemuck::cast_slice(&self.skinning_cache.inverse_bind_matrices),
                    );
                }
            }

            if !self.skinning_cache.skin_data.is_empty() {
                let required_size = self.skinning_cache.skin_data.len();
                if required_size > self.skin_data_buffer_size {
                    let new_size = (required_size as f32 * 2.0).ceil() as usize;
                    let buffer_bytes = std::mem::size_of::<GpuSkinData>() * new_size;
                    if buffer_bytes > max_buffer_size {
                        tracing::error!(
                            "Skin data buffer would exceed GPU limit: {} > {} bytes",
                            buffer_bytes,
                            max_buffer_size
                        );
                    } else {
                        self.skin_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                            label: Some("Skin Data Buffer (Resized)"),
                            size: buffer_bytes as u64,
                            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                            mapped_at_creation: false,
                        });
                        self.skin_data_buffer_size = new_size;
                        buffers_resized = true;
                    }
                }
                if self.skinning_cache.skin_data.len() <= self.skin_data_buffer_size {
                    queue.write_buffer(
                        &self.skin_data_buffer,
                        0,
                        bytemuck::cast_slice(&self.skinning_cache.skin_data),
                    );
                }
            }
        }

        let bone_transforms = self.skinning_cache.collect_bone_transforms(world);
        if !bone_transforms.is_empty() {
            let required_size = bone_transforms.len();
            if required_size > self.bone_transforms_buffer_size {
                let new_size = (required_size as f32 * 2.0).ceil() as usize;
                let buffer_bytes = matrix_size * new_size;
                if buffer_bytes > max_buffer_size {
                    tracing::error!(
                        "Bone transforms buffer would exceed GPU limit: {} > {} bytes",
                        buffer_bytes,
                        max_buffer_size
                    );
                } else {
                    self.bone_transforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Bone Transforms Buffer (Resized)"),
                        size: buffer_bytes as u64,
                        usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                        mapped_at_creation: false,
                    });
                    self.bone_transforms_buffer_size = new_size;
                    buffers_resized = true;
                }
            }
            if bone_transforms.len() <= self.bone_transforms_buffer_size {
                queue.write_buffer(
                    &self.bone_transforms_buffer,
                    0,
                    bytemuck::cast_slice(&bone_transforms),
                );
            }
        }

        let required_joints = self.skinning_cache.total_joints as usize;
        if required_joints > self.joint_matrices_buffer_size {
            let new_size = (required_joints as f32 * 2.0).ceil() as usize;
            let buffer_bytes = matrix_size * new_size;
            if buffer_bytes > max_buffer_size {
                tracing::error!(
                    "Joint matrices buffer would exceed GPU limit: {} > {} bytes",
                    buffer_bytes,
                    max_buffer_size
                );
            } else {
                self.joint_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Joint Matrices Buffer (Resized)"),
                    size: buffer_bytes as u64,
                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                    mapped_at_creation: false,
                });
                self.joint_matrices_buffer_size = new_size;
                buffers_resized = true;
                self.rebuild_instance_bind_group(device);
            }
        }

        if buffers_resized {
            self.rebuild_skinning_compute_bind_group(device);
        }

        self.total_joints_to_dispatch = self.skinning_cache.total_joints;

        for entity in world.query_entities(
            crate::ecs::world::SKIN
                | crate::ecs::world::RENDER_MESH
                | crate::ecs::world::GLOBAL_TRANSFORM,
        ) {
            if world.entity_has_components(entity, crate::ecs::world::WATER) {
                continue;
            }
            if let Some(visibility) = world.get_visibility(entity)
                && !visibility.visible
            {
                continue;
            }

            let render_mesh = match world.get_render_mesh(entity) {
                Some(rm) => rm,
                None => continue,
            };

            let mesh_id = match self.skinned_meshes.get(&render_mesh.name) {
                Some(&id) => id,
                None => continue,
            };

            let skin_index = self
                .skinning_cache
                .entity_skin_indices
                .get(&entity)
                .copied()
                .unwrap_or(0);
            let joint_offset = self.skinning_cache.get_joint_offset(skin_index);

            let material_id = materials.len() as u32;
            let material_data = if let Some(material_ref) = world.get_material_ref(entity) {
                let material_opt = if let Some(id) = material_ref.id {
                    registry_entry(
                        &world.resources.material_registry.registry,
                        id.index,
                        id.generation,
                    )
                } else {
                    registry_entry_by_name(
                        &world.resources.material_registry.registry,
                        &material_ref.name,
                    )
                };

                if let Some(material) = material_opt {
                    let alpha_mode = match material.alpha_mode {
                        crate::ecs::material::components::AlphaMode::Opaque => 0,
                        crate::ecs::material::components::AlphaMode::Mask => 1,
                        crate::ecs::material::components::AlphaMode::Blend => 2,
                    };
                    let mut flags = 0u32;
                    if material.normal_map_flip_y {
                        flags |= NORMAL_MAP_FLIP_Y;
                    }
                    if material.normal_map_two_component {
                        flags |= NORMAL_MAP_TWO_COMPONENT;
                    }
                    material_texture_map.insert(
                        material_id,
                        MaterialTextures {
                            base_texture: material.base_texture.clone(),
                            emissive_texture: material.emissive_texture.clone(),
                            normal_texture: material.normal_texture.clone(),
                            metallic_roughness_texture: material.metallic_roughness_texture.clone(),
                            occlusion_texture: material.occlusion_texture.clone(),
                            transmission_texture: material.transmission_texture.clone(),
                            thickness_texture: material.thickness_texture.clone(),
                            specular_texture: material.specular_texture.clone(),
                            specular_color_texture: material.specular_color_texture.clone(),
                        },
                    );
                    MaterialData {
                        base_color: material.base_color,
                        emissive_factor: material.emissive_factor,
                        alpha_mode,
                        alpha_cutoff: material.alpha_cutoff,
                        has_base_texture: material.base_texture.is_some() as u32,
                        has_emissive_texture: material.emissive_texture.is_some() as u32,
                        has_normal_texture: material.normal_texture.is_some() as u32,
                        has_metallic_roughness_texture: material
                            .metallic_roughness_texture
                            .is_some()
                            as u32,
                        has_occlusion_texture: material.occlusion_texture.is_some() as u32,
                        normal_scale: material.normal_scale,
                        occlusion_strength: material.occlusion_strength,
                        roughness: material.roughness,
                        metallic: material.metallic,
                        unlit: material.unlit as u32,
                        normal_map_flags: flags,
                        uv_scale: material.uv_scale,
                        transmission_factor: material.transmission_factor,
                        has_transmission_texture: material.transmission_texture.is_some() as u32,
                        thickness: material.thickness,
                        has_thickness_texture: material.thickness_texture.is_some() as u32,
                        _align_attenuation: [0, 0],
                        attenuation_color: material.attenuation_color,
                        attenuation_distance: material.attenuation_distance,
                        ior: material.ior,
                        specular_factor: material.specular_factor,
                        _align_specular: [0, 0],
                        specular_color_factor: material.specular_color_factor,
                        has_specular_texture: material.specular_texture.is_some() as u32,
                        has_specular_color_texture: material.specular_color_texture.is_some()
                            as u32,
                        emissive_strength: material.emissive_strength,
                        _padding: [0, 0],
                    }
                } else {
                    MaterialData {
                        base_color: [1.0, 1.0, 1.0, 1.0],
                        emissive_factor: [0.0, 0.0, 0.0],
                        alpha_mode: 0,
                        alpha_cutoff: 0.5,
                        has_base_texture: 0,
                        has_emissive_texture: 0,
                        has_normal_texture: 0,
                        has_metallic_roughness_texture: 0,
                        has_occlusion_texture: 0,
                        normal_scale: 1.0,
                        occlusion_strength: 1.0,
                        roughness: 0.5,
                        metallic: 0.0,
                        unlit: 0,
                        normal_map_flags: 0,
                        uv_scale: [1.0, 1.0],
                        transmission_factor: 0.0,
                        has_transmission_texture: 0,
                        thickness: 0.0,
                        has_thickness_texture: 0,
                        _align_attenuation: [0, 0],
                        attenuation_color: [1.0, 1.0, 1.0],
                        attenuation_distance: f32::INFINITY,
                        ior: 1.5,
                        specular_factor: 1.0,
                        _align_specular: [0, 0],
                        specular_color_factor: [1.0, 1.0, 1.0],
                        has_specular_texture: 0,
                        has_specular_color_texture: 0,
                        emissive_strength: 1.0,
                        _padding: [0, 0],
                    }
                }
            } else {
                MaterialData {
                    base_color: [1.0, 1.0, 1.0, 1.0],
                    emissive_factor: [0.0, 0.0, 0.0],
                    alpha_mode: 0,
                    alpha_cutoff: 0.5,
                    has_base_texture: 0,
                    has_emissive_texture: 0,
                    has_normal_texture: 0,
                    has_metallic_roughness_texture: 0,
                    has_occlusion_texture: 0,
                    normal_scale: 1.0,
                    occlusion_strength: 1.0,
                    roughness: 0.5,
                    metallic: 0.0,
                    unlit: 0,
                    normal_map_flags: 0,
                    uv_scale: [1.0, 1.0],
                    transmission_factor: 0.0,
                    has_transmission_texture: 0,
                    thickness: 0.0,
                    has_thickness_texture: 0,
                    _align_attenuation: [0, 0],
                    attenuation_color: [1.0, 1.0, 1.0],
                    attenuation_distance: f32::INFINITY,
                    ior: 1.5,
                    specular_factor: 1.0,
                    _align_specular: [0, 0],
                    specular_color_factor: [1.0, 1.0, 1.0],
                    has_specular_texture: 0,
                    has_specular_color_texture: 0,
                    emissive_strength: 1.0,
                    _padding: [0, 0],
                }
            };

            materials.push(material_data);
            custom_data.push([1.0f32, 1.0, 1.0, 1.0]);

            let mesh_data = &self.skinned_mesh_data[mesh_id as usize];
            let mut morph_weights = [0.0f32; 8];
            if let Some(morph_component) = world.get_morph_weights(entity) {
                for (index, weight) in morph_component.weights.iter().take(8).enumerate() {
                    morph_weights[index] = *weight;
                }
            }

            objects.push(SkinnedObjectData {
                transform_index: 0,
                mesh_id,
                material_id,
                joint_offset,
                morph_weights,
                morph_target_count: mesh_data.morph_target_count,
                morph_displacement_offset: mesh_data.morph_displacement_offset,
                mesh_vertex_offset: mesh_data.vertex_offset,
                mesh_vertex_count: mesh_data.vertex_count,
            });

            self.skinned_entities.push(entity);
        }

        if objects.is_empty() {
            return;
        }

        if objects.len() > self.object_buffer_size {
            let new_size = (objects.len() as f32 * 2.0).ceil() as usize;
            self.object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Skinned Mesh Object Buffer (Resized)"),
                size: (std::mem::size_of::<SkinnedObjectData>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.object_buffer_size = new_size;
            self.rebuild_instance_bind_group(device);
        }

        if materials.len() > self.materials_buffer_size {
            let new_size = (materials.len() as f32 * 2.0).ceil() as usize;
            self.materials_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Skinned Mesh Materials Buffer (Resized)"),
                size: (std::mem::size_of::<MaterialData>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.materials_buffer_size = new_size;
            self.rebuild_instance_bind_group(device);
        }

        if custom_data.len() > self.custom_data_buffer_size {
            let new_size = (custom_data.len() as f32 * 2.0).ceil() as usize;
            self.custom_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Skinned Mesh Custom Data Buffer (Resized)"),
                size: (std::mem::size_of::<[f32; 4]>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.custom_data_buffer_size = new_size;
            self.rebuild_instance_bind_group(device);
        }

        queue.write_buffer(&self.object_buffer, 0, bytemuck::cast_slice(&objects));
        queue.write_buffer(&self.materials_buffer, 0, bytemuck::cast_slice(&materials));
        queue.write_buffer(
            &self.custom_data_buffer,
            0,
            bytemuck::cast_slice(&custom_data),
        );

        self.sync_textures(&world.resources.texture_cache);

        for material_id in 0..materials.len() as u32 {
            let cache_key = material_texture_map
                .get(&material_id)
                .cloned()
                .unwrap_or_default();

            if self.material_bind_groups.contains_key(&material_id)
                && self.material_bind_group_cache_key.get(&material_id) == Some(&cache_key)
            {
                continue;
            }

            let (base_texture_view, base_sampler) = if let Some(ref name) = cache_key.base_texture {
                self.registered_textures
                    .get(name)
                    .map(|(v, s)| (v, s))
                    .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
            } else {
                (&self.dummy_white_view, &self.dummy_sampler)
            };

            let (emissive_texture_view, emissive_sampler) =
                if let Some(ref name) = cache_key.emissive_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_black_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_black_view, &self.dummy_sampler)
                };

            let (normal_texture_view, normal_sampler) =
                if let Some(ref name) = cache_key.normal_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_normal_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_normal_view, &self.dummy_sampler)
                };

            let (metallic_roughness_view, metallic_roughness_sampler) =
                if let Some(ref name) = cache_key.metallic_roughness_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let (occlusion_texture_view, occlusion_sampler) =
                if let Some(ref name) = cache_key.occlusion_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let (transmission_texture_view, transmission_sampler) =
                if let Some(ref name) = cache_key.transmission_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let (thickness_texture_view, thickness_sampler) =
                if let Some(ref name) = cache_key.thickness_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let (specular_texture_view, specular_sampler) =
                if let Some(ref name) = cache_key.specular_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let (specular_color_texture_view, specular_color_sampler) =
                if let Some(ref name) = cache_key.specular_color_texture {
                    self.registered_textures
                        .get(name)
                        .map(|(v, s)| (v, s))
                        .unwrap_or((&self.dummy_white_view, &self.dummy_sampler))
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some(&format!(
                    "Skinned Material {} Texture Bind Group",
                    material_id
                )),
                layout: &self.texture_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(base_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(base_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(emissive_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(emissive_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: wgpu::BindingResource::TextureView(normal_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::Sampler(normal_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::TextureView(metallic_roughness_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: wgpu::BindingResource::Sampler(metallic_roughness_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: wgpu::BindingResource::TextureView(occlusion_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 9,
                        resource: wgpu::BindingResource::Sampler(occlusion_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 10,
                        resource: wgpu::BindingResource::TextureView(transmission_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 11,
                        resource: wgpu::BindingResource::Sampler(transmission_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 12,
                        resource: wgpu::BindingResource::TextureView(thickness_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 13,
                        resource: wgpu::BindingResource::Sampler(thickness_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 14,
                        resource: wgpu::BindingResource::TextureView(specular_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 15,
                        resource: wgpu::BindingResource::Sampler(specular_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 16,
                        resource: wgpu::BindingResource::TextureView(specular_color_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 17,
                        resource: wgpu::BindingResource::Sampler(specular_color_sampler),
                    },
                ],
            });
            self.material_bind_groups.insert(material_id, bind_group);
            self.material_bind_group_cache_key
                .insert(material_id, cache_key);
        }

        let light_result = collect_lights(world, MAX_LIGHTS);
        let mut lights_data = light_result.lights_data;
        let directional_light = light_result.directional_light;

        let directional_light_direction = directional_light
            .as_ref()
            .map(|(_light, transform)| {
                let dir = transform.forward_vector();
                [dir.x, dir.y, dir.z, 0.0]
            })
            .unwrap_or([0.0, -1.0, 0.0, 0.0]);

        let camera_position = world
            .resources
            .active_camera
            .and_then(|cam| world.get_global_transform(cam))
            .map(|t| nalgebra_glm::vec3(t.0[(0, 3)], t.0[(1, 3)], t.0[(2, 3)]))
            .unwrap_or_else(|| nalgebra_glm::vec3(0.0, 0.0, 0.0));

        let spotlight_result = collect_spotlight_shadows(world, camera_position);
        apply_spotlight_shadow_indices(
            world,
            &mut lights_data,
            &spotlight_result.entity_to_shadow_index,
        );

        if !spotlight_result.shadow_data.is_empty() {
            queue.write_buffer(
                &self.spotlight_shadow_buffer,
                0,
                bytemuck::cast_slice(&spotlight_result.shadow_data),
            );
        }

        if lights_data.len() > self.lights_buffer_size {
            let new_size = (lights_data.len() as f32 * 2.0).ceil() as usize;
            self.lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Skinned Mesh Lights Buffer (Resized)"),
                size: (std::mem::size_of::<LightData>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.lights_buffer_size = new_size;
            self.rebuild_instance_bind_group(device);
        }

        if !lights_data.is_empty() {
            queue.write_buffer(&self.lights_buffer, 0, bytemuck::cast_slice(&lights_data));
        }

        let cascade_result = calculate_cascade_shadows(world, directional_light.as_ref());
        let cascade_view_projections = cascade_result.cascade_view_projections;
        let cascade_diameters = cascade_result.cascade_diameters;
        let light_view_projection = cascade_result.light_view_projection;
        let shadow_bias = cascade_result.shadow_bias;
        let shadows_enabled = cascade_result.shadows_enabled;

        let cascade_texture_resolution = if cfg!(target_arch = "wasm32") {
            2048.0
        } else {
            4096.0
        };

        let cascade_atlas_offsets: [[f32; 4]; crate::render::wgpu::passes::NUM_SHADOW_CASCADES] = [
            [
                0.0,
                0.0,
                cascade_diameters[0] / cascade_texture_resolution,
                0.0,
            ],
            [
                0.5,
                0.0,
                cascade_diameters[1] / cascade_texture_resolution,
                0.0,
            ],
            [
                0.0,
                0.5,
                cascade_diameters[2] / cascade_texture_resolution,
                0.0,
            ],
            [
                0.5,
                0.5,
                cascade_diameters[3] / cascade_texture_resolution,
                0.0,
            ],
        ];

        if let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        {
            let global_unlit = if world.resources.graphics.unlit_mode {
                1.0
            } else {
                0.0
            };

            let (snap_resolution, snap_enabled) =
                if let Some(ref vertex_snap) = world.resources.graphics.vertex_snap {
                    (vertex_snap.resolution, 1)
                } else {
                    ([320.0, 240.0], 0)
                };

            let affine_enabled = if world.resources.graphics.affine_texture_mapping {
                1
            } else {
                0
            };

            let (fog_color, fog_enabled, fog_start, fog_end) =
                if let Some(ref fog) = world.resources.graphics.fog {
                    (fog.color, 1, fog.start, fog.end)
                } else {
                    ([0.5, 0.5, 0.6], 0, 5.0, 30.0)
                };

            let time = world.resources.window.timing.uptime_milliseconds as f32 / 1000.0;

            let uniforms = SkinnedMeshUniforms {
                view: camera_matrices.view.into(),
                projection: camera_matrices.projection.into(),
                camera_position: [
                    camera_matrices.camera_position.x,
                    camera_matrices.camera_position.y,
                    camera_matrices.camera_position.z,
                    1.0,
                ],
                num_lights: [lights_data.len() as u32, 0, 0, 0],
                ambient_light: [0.1, 0.1, 0.1, 1.0],
                light_view_projection,
                shadow_bias,
                shadows_enabled,
                global_unlit,
                shadow_normal_bias: 1.8,
                snap_resolution,
                snap_enabled,
                affine_enabled,
                fog_color,
                fog_enabled,
                fog_start,
                fog_end,
                cascade_count: crate::render::wgpu::passes::NUM_SHADOW_CASCADES as u32,
                _padding2: 0.0,
                cascade_view_projections,
                cascade_split_distances: CASCADE_SPLIT_DISTANCES,
                cascade_atlas_offsets,
                cascade_atlas_scale: [0.5, 0.5, 0.0, 0.0],
                time,
                _padding_align: [0.0; 3],
                directional_light_direction,
                _padding3: [0.0; 20],
            };

            queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
        }
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        if self.skinned_entities.is_empty() {
            return Ok(vec![]);
        }

        if self.total_joints_to_dispatch > 0 {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Skinning Compute Pass"),
                        timestamp_writes: None,
                    });

            compute_pass.set_pipeline(&self.skinning_compute_pipeline);
            compute_pass.set_bind_group(0, &self.skinning_compute_bind_group, &[]);

            let num_workgroups = self.total_joints_to_dispatch.div_ceil(64);
            compute_pass.dispatch_workgroups(num_workgroups, 1, 1);
        }

        let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
        let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;
        let (shadow_depth_view, _, _) = context.get_depth_attachment("shadow_depth")?;
        let (spotlight_shadow_atlas_view, _, _) =
            context.get_depth_attachment("spotlight_shadow_atlas")?;

        let brdf_view = self
            .brdf_lut_view
            .as_ref()
            .unwrap_or(&self.dummy_white_view);
        let irradiance_view = self
            .irradiance_map_view
            .as_ref()
            .unwrap_or(&self.dummy_cube_view);
        let prefiltered_view = self
            .prefiltered_env_view
            .as_ref()
            .unwrap_or(&self.dummy_cube_view);

        self.shadow_bind_group = context
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Skinned Mesh Shadow Bind Group"),
                layout: &self.shadow_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(shadow_depth_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(spotlight_shadow_atlas_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: self.spotlight_shadow_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::TextureView(brdf_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: wgpu::BindingResource::TextureView(irradiance_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 9,
                        resource: wgpu::BindingResource::TextureView(prefiltered_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 10,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 11,
                        resource: wgpu::BindingResource::TextureView(
                            &self.point_shadow_cubemap_view,
                        ),
                    },
                    wgpu::BindGroupEntry {
                        binding: 12,
                        resource: wgpu::BindingResource::Sampler(&self.point_shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 13,
                        resource: self.point_shadow_buffer.as_entire_binding(),
                    },
                ],
            });

        let mut render_pass = context
            .encoder
            .begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Skinned Mesh Pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: color_view,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: color_load,
                        store: color_store,
                    },
                    depth_slice: None,
                })],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: depth_view,
                    depth_ops: Some(wgpu::Operations {
                        load: depth_load,
                        store: depth_store,
                    }),
                    stencil_ops: None,
                }),
                timestamp_writes: None,
                occlusion_query_set: None,
            });

        render_pass.set_pipeline(&self.render_pipeline);
        render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
        render_pass.set_bind_group(1, &self.instance_bind_group, &[]);

        let default_bind_group = context
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Skinned Mesh Default Texture Bind Group"),
                layout: &self.texture_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_black_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_normal_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 9,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 10,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 11,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 12,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 13,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 14,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 15,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 16,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 17,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                ],
            });
        render_pass.set_bind_group(3, &self.shadow_bind_group, &[]);

        render_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
        render_pass.set_index_buffer(
            self.skinned_index_buffer.slice(..),
            wgpu::IndexFormat::Uint32,
        );

        for (instance_index, &entity) in self.skinned_entities.iter().enumerate() {
            let mesh_name = context
                .configs
                .get_render_mesh(entity)
                .map(|rm| rm.name.as_str());

            if let Some(name) = mesh_name
                && let Some(&mesh_id) = self.skinned_meshes.get(name)
            {
                let mesh_data = &self.skinned_mesh_data[mesh_id as usize];
                if mesh_data.index_count > 0 {
                    let material_id = instance_index as u32;
                    if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
                        render_pass.set_bind_group(2, bind_group, &[]);
                    } else {
                        render_pass.set_bind_group(2, &default_bind_group, &[]);
                    }
                    render_pass.draw_indexed(
                        mesh_data.index_offset..(mesh_data.index_offset + mesh_data.index_count),
                        mesh_data.vertex_offset as i32,
                        instance_index as u32..(instance_index as u32 + 1),
                    );
                }
            }
        }

        drop(render_pass);

        Ok(vec![])
    }
}
