use crate::ecs::skin::systems::GpuSkinData;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use wgpu::util::DeviceExt;

use super::cascade::{
    calculate_cascade_view_projection, get_frustum_corners_world_space, reverse_z_ortho_light,
    reverse_z_perspective,
};
use super::pass::ShadowDepthPass;
use super::types::{
    CASCADE_ATLAS_SLOTS_PER_ROW, CASCADE_SPLIT_DISTANCES, MAX_POINT_LIGHT_SHADOWS,
    MAX_SPOTLIGHT_SHADOWS, NUM_SHADOW_CASCADES, POINT_SHADOW_FACE_SIZE, POINT_SHADOW_NUM_FACES,
    PointLightShadowData, PointLightShadowSlot, PointShadowUniforms, SPOTLIGHT_ATLAS_SLOTS_PER_ROW,
    ShadowUniforms, SkinnedShadowObjectData, SpotlightShadowData, SpotlightShadowSlot,
};

const Y_FLIP_MATRIX: nalgebra_glm::Mat4 = nalgebra_glm::Mat4::new(
    1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
);

const CUBEMAP_FACE_DIRECTIONS: [(nalgebra_glm::Vec3, nalgebra_glm::Vec3); POINT_SHADOW_NUM_FACES] = [
    (
        nalgebra_glm::Vec3::new(1.0, 0.0, 0.0),
        nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
    ),
    (
        nalgebra_glm::Vec3::new(-1.0, 0.0, 0.0),
        nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
    ),
    (
        nalgebra_glm::Vec3::new(0.0, 1.0, 0.0),
        nalgebra_glm::Vec3::new(0.0, 0.0, 1.0),
    ),
    (
        nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
        nalgebra_glm::Vec3::new(0.0, 0.0, -1.0),
    ),
    (
        nalgebra_glm::Vec3::new(0.0, 0.0, 1.0),
        nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
    ),
    (
        nalgebra_glm::Vec3::new(0.0, 0.0, -1.0),
        nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
    ),
];

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

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

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

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

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

        let directional_light = world
            .query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
            .find_map(|entity| {
                if let (Some(light), Some(transform)) =
                    (world.get_light(entity), world.get_global_transform(entity))
                {
                    if matches!(
                        light.light_type,
                        crate::ecs::light::components::LightType::Directional
                    ) && light.cast_shadows
                    {
                        Some((light.clone(), *transform))
                    } else {
                        None
                    }
                } else {
                    None
                }
            });

        if let Some((_light, light_transform)) = directional_light {
            let light_direction = light_transform.forward_vector();

            let camera_matrices = crate::ecs::camera::queries::query_active_camera_matrices(world);

            if let Some(camera_matrices) = camera_matrices {
                let active_camera = world.resources.active_camera;
                let camera = active_camera.and_then(|entity| world.get_camera(entity));

                let (fov, aspect, camera_near) = if let Some(camera) = camera {
                    match &camera.projection {
                        crate::ecs::camera::components::Projection::Perspective(persp) => {
                            let aspect = persp.aspect_ratio.unwrap_or_else(|| {
                                crate::ecs::camera::queries::query_window_aspect_ratio(world)
                                    .unwrap_or(1.78)
                            });
                            (persp.y_fov_rad, aspect, persp.z_near)
                        }
                        crate::ecs::camera::components::Projection::Orthographic(_) => {
                            (std::f32::consts::FRAC_PI_4, 1.78, 0.1)
                        }
                    }
                } else {
                    (std::f32::consts::FRAC_PI_4, 1.78, 0.1)
                };

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

                for cascade_index in 0..NUM_SHADOW_CASCADES {
                    let cascade_near = if cascade_index == 0 {
                        camera_near
                    } else {
                        CASCADE_SPLIT_DISTANCES[cascade_index - 1]
                    };
                    let cascade_far = CASCADE_SPLIT_DISTANCES[cascade_index];

                    let frustum_corners = get_frustum_corners_world_space(
                        &camera_matrices.view,
                        fov,
                        aspect,
                        cascade_near,
                        cascade_far,
                    );

                    let view_projection = calculate_cascade_view_projection(
                        &frustum_corners,
                        &light_direction,
                        cascade_resolution,
                        cascade_far,
                    );

                    self.cascade_data.view_projections[cascade_index] = view_projection.into();
                }
            } else {
                let up = if light_direction.y.abs() > 0.99 {
                    nalgebra_glm::vec3(1.0, 0.0, 0.0)
                } else {
                    nalgebra_glm::vec3(0.0, 1.0, 0.0)
                };

                for (cascade_index, &cascade_far) in CASCADE_SPLIT_DISTANCES.iter().enumerate() {
                    let half_size = cascade_far * 0.5;

                    let scene_center = nalgebra_glm::vec3(0.0, 0.0, 0.0);
                    let light_position = scene_center - light_direction * 100.0;
                    let light_view = nalgebra_glm::look_at(&light_position, &scene_center, &up);
                    let light_projection = reverse_z_ortho_light(
                        -half_size,
                        half_size,
                        -half_size,
                        half_size,
                        0.1,
                        cascade_far * 2.0,
                    );

                    self.cascade_data.view_projections[cascade_index] =
                        (light_projection * light_view).into();
                }
            }

            let first_cascade_vp: nalgebra_glm::Mat4 = self.cascade_data.view_projections[0].into();
            let uniforms = ShadowUniforms {
                light_view_projection: first_cascade_vp.into(),
                _padding: [[0.0; 4]; 4],
                _padding2: [[0.0; 4]; 4],
                _padding3: [[0.0; 4]; 4],
                _padding4: [0.0; 4],
            };

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

            for cascade_index in 0..NUM_SHADOW_CASCADES {
                let cascade_vp: nalgebra_glm::Mat4 =
                    self.cascade_data.view_projections[cascade_index].into();
                let cascade_uniforms = ShadowUniforms {
                    light_view_projection: cascade_vp.into(),
                    _padding: [[0.0; 4]; 4],
                    _padding2: [[0.0; 4]; 4],
                    _padding3: [[0.0; 4]; 4],
                    _padding4: [0.0; 4],
                };
                queue.write_buffer(
                    &self.cascade_uniform_buffers[cascade_index],
                    0,
                    bytemuck::cast_slice(&[cascade_uniforms]),
                );
            }
        }

        self.shadow_caster_entities.clear();
        let mut transforms = Vec::new();

        for entity in world.query_entities(
            crate::ecs::world::RENDER_MESH
                | crate::ecs::world::GLOBAL_TRANSFORM
                | crate::ecs::world::CASTS_SHADOW,
        ) {
            let render_layer = world
                .get_render_layer(entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            if render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY {
                continue;
            }

            if let (Some(render_mesh), Some(transform)) = (
                world.get_render_mesh(entity),
                world.get_global_transform(entity),
            ) && self.meshes.contains_key(&render_mesh.name)
            {
                let model_matrix: [[f32; 4]; 4] = transform.0.into();
                transforms.push(model_matrix);
                self.shadow_caster_entities.push(entity);
            }
        }

        for entity in world.query_entities(
            crate::ecs::world::INSTANCED_MESH
                | crate::ecs::world::GLOBAL_TRANSFORM
                | crate::ecs::world::CASTS_SHADOW,
        ) {
            let render_layer = world
                .get_render_layer(entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            if render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY {
                continue;
            }

            if let Some(visibility) = world.get_visibility(entity)
                && !visibility.visible
            {
                continue;
            }

            if let Some(instanced_mesh) = world.get_instanced_mesh(entity) {
                if !self.meshes.contains_key(&instanced_mesh.mesh_name) {
                    continue;
                }

                let cached_matrices = instanced_mesh.cached_model_matrices();
                for matrix in cached_matrices {
                    transforms.push(matrix.model);
                    self.shadow_caster_entities.push(entity);
                }
            }
        }

        if !transforms.is_empty() {
            if transforms.len() > self.transform_buffer_size {
                let new_size = (transforms.len() as f32 * 2.0).ceil() as usize;
                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Shadow Transform Buffer (Resized)"),
                    size: (std::mem::size_of::<[[f32; 4]; 4]>() * new_size) as u64,
                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                    mapped_at_creation: false,
                });

                self.transform_buffer = new_buffer;
                self.transform_buffer_size = new_size;

                self.transform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                    label: Some("Shadow Transform Bind Group"),
                    layout: &self.transform_bind_group_layout,
                    entries: &[wgpu::BindGroupEntry {
                        binding: 0,
                        resource: self.transform_buffer.as_entire_binding(),
                    }],
                });
            }

            queue.write_buffer(&self.transform_buffer, 0, bytemuck::cast_slice(&transforms));
        }

        self.sync_skinned_meshes(device, queue, &world.resources.mesh_cache);

        self.skinned_shadow_caster_entities.clear();
        let mut skinned_objects = Vec::new();

        let skinning_data = crate::ecs::skin::systems::collect_skinning_data(world);

        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 !skinning_data.bone_transforms.is_empty() {
            let required_size = skinning_data.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!(
                        "Shadow 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("Shadow 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 skinning_data.bone_transforms.len() <= self.bone_transforms_buffer_size {
                queue.write_buffer(
                    &self.bone_transforms_buffer,
                    0,
                    bytemuck::cast_slice(&skinning_data.bone_transforms),
                );
            }
        }

        if !skinning_data.inverse_bind_matrices.is_empty() {
            let required_size = skinning_data.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!(
                        "Shadow 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("Shadow 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 skinning_data.inverse_bind_matrices.len() <= self.inverse_bind_matrices_buffer_size {
                queue.write_buffer(
                    &self.inverse_bind_matrices_buffer,
                    0,
                    bytemuck::cast_slice(&skinning_data.inverse_bind_matrices),
                );
            }
        }

        if !skinning_data.skin_data.is_empty() {
            let required_size = skinning_data.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!(
                        "Shadow 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("Shadow 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 skinning_data.skin_data.len() <= self.skin_data_buffer_size {
                queue.write_buffer(
                    &self.skin_data_buffer,
                    0,
                    bytemuck::cast_slice(&skinning_data.skin_data),
                );
            }
        }

        let required_joints = skinning_data.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!(
                    "Shadow 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("Shadow 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_skinned_bind_group(device);
            }
        }

        if buffers_resized {
            self.rebuild_skinning_compute_bind_group(device);
        }

        self.total_joints_to_dispatch = skinning_data.total_joints;

        for entity in world.query_entities(
            crate::ecs::world::SKIN
                | crate::ecs::world::RENDER_MESH
                | crate::ecs::world::GLOBAL_TRANSFORM
                | crate::ecs::world::CASTS_SHADOW,
        ) {
            let render_layer = world
                .get_render_layer(entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            if render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY {
                continue;
            }

            if let Some(render_mesh) = world.get_render_mesh(entity)
                && let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
                    self.skinned_meshes.get(&render_mesh.name)
            {
                let skin_index = skinning_data
                    .entity_skin_indices
                    .get(&entity)
                    .copied()
                    .unwrap_or(0);
                let joint_offset = skinning_data.get_joint_offset(skin_index);

                skinned_objects.push(SkinnedShadowObjectData {
                    transform_index: 0,
                    mesh_id: (index_offset << 16) | index_count,
                    material_id: vertex_offset,
                    joint_offset,
                });
                self.skinned_shadow_caster_entities.push(entity);
            }
        }

        if !skinned_objects.is_empty() {
            if skinned_objects.len() > self.skinned_object_buffer_size {
                let new_size = (skinned_objects.len() as f32 * 2.0).ceil() as usize;
                self.skinned_object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Shadow Skinned Object Buffer (Resized)"),
                    size: (std::mem::size_of::<SkinnedShadowObjectData>() * new_size) as u64,
                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                    mapped_at_creation: false,
                });
                self.skinned_object_buffer_size = new_size;
                self.rebuild_skinned_bind_group(device);
            }

            queue.write_buffer(
                &self.skinned_object_buffer,
                0,
                bytemuck::cast_slice(&skinned_objects),
            );
        }

        self.spotlight_shadow_slots.clear();
        self.spotlight_shadow_data.clear();

        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 mut spotlight_candidates: Vec<(
            crate::ecs::world::Entity,
            f32,
            crate::ecs::light::components::Light,
            crate::ecs::transform::components::GlobalTransform,
        )> = Vec::new();

        for entity in
            world.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
        {
            if let (Some(light), Some(transform)) =
                (world.get_light(entity), world.get_global_transform(entity))
                && matches!(
                    light.light_type,
                    crate::ecs::light::components::LightType::Spot
                )
                && light.cast_shadows
            {
                let light_pos = nalgebra_glm::vec3(
                    transform.0[(0, 3)],
                    transform.0[(1, 3)],
                    transform.0[(2, 3)],
                );
                let distance_sq = nalgebra_glm::length2(&(light_pos - camera_position));
                spotlight_candidates.push((entity, distance_sq, light.clone(), *transform));
            }
        }

        spotlight_candidates
            .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));

        let atlas_size = if cfg!(target_arch = "wasm32") {
            1024
        } else {
            4096
        };
        self.spotlight_atlas_slot_size = atlas_size / SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
        let slot_scale = 1.0 / SPOTLIGHT_ATLAS_SLOTS_PER_ROW as f32;

        for (slot_index, (entity, _distance, light, transform)) in spotlight_candidates
            .iter()
            .take(MAX_SPOTLIGHT_SHADOWS)
            .enumerate()
        {
            let light_position = nalgebra_glm::vec3(
                transform.0[(0, 3)],
                transform.0[(1, 3)],
                transform.0[(2, 3)],
            );
            let light_direction = transform.forward_vector();

            let up = if light_direction.y.abs() > 0.99 {
                nalgebra_glm::vec3(1.0, 0.0, 0.0)
            } else {
                nalgebra_glm::vec3(0.0, 1.0, 0.0)
            };

            let target = light_position + light_direction;
            let light_view = nalgebra_glm::look_at(&light_position, &target, &up);

            let fov = light.outer_cone_angle * 2.0;
            let near = 0.1;
            let far = light.range.max(1.0);
            let light_projection = reverse_z_perspective(fov, 1.0, near, far);

            let view_projection = light_projection * light_view;

            let slot_x = (slot_index as u32) % SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
            let slot_y = (slot_index as u32) / SPOTLIGHT_ATLAS_SLOTS_PER_ROW;

            self.spotlight_shadow_slots.push(SpotlightShadowSlot {
                entity: *entity,
                view_projection,
                slot_index: slot_index as u32,
                bias: light.shadow_bias,
            });

            self.spotlight_shadow_data.push(SpotlightShadowData {
                view_projection: view_projection.into(),
                atlas_offset: [slot_x as f32 * slot_scale, slot_y as f32 * slot_scale],
                atlas_scale: [slot_scale, slot_scale],
                bias: light.shadow_bias,
                _padding: [0.0; 3],
            });
        }

        self.point_light_shadow_slots.clear();
        self.point_light_shadow_data.clear();

        let mut point_light_candidates: Vec<(
            crate::ecs::world::Entity,
            f32,
            crate::ecs::light::components::Light,
            crate::ecs::transform::components::GlobalTransform,
        )> = Vec::new();

        for entity in
            world.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
        {
            if let (Some(light), Some(transform)) =
                (world.get_light(entity), world.get_global_transform(entity))
                && matches!(
                    light.light_type,
                    crate::ecs::light::components::LightType::Point
                )
                && light.cast_shadows
            {
                let light_pos = nalgebra_glm::vec3(
                    transform.0[(0, 3)],
                    transform.0[(1, 3)],
                    transform.0[(2, 3)],
                );
                let distance_sq = nalgebra_glm::length2(&(light_pos - camera_position));
                point_light_candidates.push((entity, distance_sq, light.clone(), *transform));
            }
        }

        point_light_candidates
            .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));

        for (slot_index, (entity, _distance, light, transform)) in point_light_candidates
            .iter()
            .take(MAX_POINT_LIGHT_SHADOWS)
            .enumerate()
        {
            let light_position = nalgebra_glm::vec3(
                transform.0[(0, 3)],
                transform.0[(1, 3)],
                transform.0[(2, 3)],
            );

            self.point_light_shadow_slots.push(PointLightShadowSlot {
                entity: *entity,
                position: light_position,
                range: light.range.max(0.1),
                slot_index: slot_index as u32,
                bias: light.shadow_bias,
            });

            self.point_light_shadow_data.push(PointLightShadowData {
                position: [light_position.x, light_position.y, light_position.z],
                range: light.range.max(0.1),
                bias: light.shadow_bias,
                shadow_index: slot_index as i32,
                _padding: [0.0; 2],
            });
        }

        if !self.point_light_shadow_slots.is_empty() {
            let mut all_uniforms: Vec<PointShadowUniforms> = Vec::new();

            for slot in &self.point_light_shadow_slots {
                let light_pos = slot.position;
                let light_range = slot.range;
                let near = 0.1;

                let base_projection =
                    reverse_z_perspective(std::f32::consts::FRAC_PI_2, 1.0, near, light_range);

                let projection = Y_FLIP_MATRIX * base_projection;

                for (direction, up) in &CUBEMAP_FACE_DIRECTIONS {
                    let target = light_pos + *direction;
                    let view = nalgebra_glm::look_at(&light_pos, &target, up);
                    let view_projection = projection * view;

                    let mut uniform: PointShadowUniforms = bytemuck::Zeroable::zeroed();
                    uniform.view_projection = view_projection.into();
                    uniform.light_position = [light_pos.x, light_pos.y, light_pos.z];
                    uniform.light_range = light_range;
                    all_uniforms.push(uniform);
                }
            }

            queue.write_buffer(
                &self.point_light_uniform_buffer,
                0,
                bytemuck::cast_slice(&all_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>>,
    > {
        let has_directional_shadow = context
            .configs
            .query_entities(crate::ecs::world::LIGHT)
            .any(|entity| {
                if let Some(light) = context.configs.get_light(entity) {
                    matches!(
                        light.light_type,
                        crate::ecs::light::components::LightType::Directional
                    ) && light.cast_shadows
                } else {
                    false
                }
            });

        let has_spotlight_shadows = !self.spotlight_shadow_slots.is_empty();
        let has_point_light_shadows = !self.point_light_shadow_slots.is_empty();

        if !has_directional_shadow && !has_spotlight_shadows && !has_point_light_shadows {
            return Ok(vec![]);
        }

        if self.total_joints_to_dispatch > 0 && !self.skinned_shadow_caster_entities.is_empty() {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Shadow 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 (shadow_depth_view, _, _) = context.get_depth_attachment("shadow_depth")?;

        let atlas_size = if cfg!(target_arch = "wasm32") {
            4096.0
        } else {
            8192.0
        };
        let cascade_slot_size = atlas_size / CASCADE_ATLAS_SLOTS_PER_ROW as f32;

        for cascade_index in 0..NUM_SHADOW_CASCADES {
            let slot_x = (cascade_index as u32) % CASCADE_ATLAS_SLOTS_PER_ROW;
            let slot_y = (cascade_index as u32) / CASCADE_ATLAS_SLOTS_PER_ROW;

            let load_op = if cascade_index == 0 {
                wgpu::LoadOp::Clear(0.0)
            } else {
                wgpu::LoadOp::Load
            };

            let mut shadow_pass = context
                .encoder
                .begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("Shadow Depth Pass"),
                    color_attachments: &[],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: shadow_depth_view,
                        depth_ops: Some(wgpu::Operations {
                            load: load_op,
                            store: wgpu::StoreOp::Store,
                        }),
                        stencil_ops: None,
                    }),
                    timestamp_writes: None,
                    occlusion_query_set: None,
                });

            shadow_pass.set_viewport(
                slot_x as f32 * cascade_slot_size,
                slot_y as f32 * cascade_slot_size,
                cascade_slot_size,
                cascade_slot_size,
                0.0,
                1.0,
            );

            shadow_pass.set_scissor_rect(
                (slot_x as f32 * cascade_slot_size) as u32,
                (slot_y as f32 * cascade_slot_size) as u32,
                cascade_slot_size as u32,
                cascade_slot_size as u32,
            );

            shadow_pass.set_pipeline(&self.pipeline);
            shadow_pass.set_bind_group(0, &self.cascade_uniform_bind_groups[cascade_index], &[]);
            shadow_pass.set_bind_group(1, &self.transform_bind_group, &[]);
            shadow_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            shadow_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

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

                if let Some(name) = mesh_name
                    && let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
                        self.meshes.get(name)
                {
                    shadow_pass.draw_indexed(
                        index_offset..(index_offset + index_count),
                        vertex_offset as i32,
                        instance_index as u32..(instance_index as u32 + 1),
                    );
                }
            }

            if !self.skinned_shadow_caster_entities.is_empty() {
                shadow_pass.set_pipeline(&self.skinned_pipeline);
                shadow_pass.set_bind_group(
                    0,
                    &self.cascade_uniform_bind_groups[cascade_index],
                    &[],
                );
                shadow_pass.set_bind_group(1, &self.skinned_bind_group, &[]);
                shadow_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
                shadow_pass.set_index_buffer(
                    self.skinned_index_buffer.slice(..),
                    wgpu::IndexFormat::Uint32,
                );

                for (instance_index, &entity) in
                    self.skinned_shadow_caster_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(&(vertex_offset, _vertex_count, index_offset, index_count)) =
                            self.skinned_meshes.get(name)
                    {
                        shadow_pass.draw_indexed(
                            index_offset..(index_offset + index_count),
                            vertex_offset as i32,
                            instance_index as u32..(instance_index as u32 + 1),
                        );
                    }
                }
            }
        }

        if has_spotlight_shadows {
            let (spotlight_atlas_view, _, _) =
                context.get_depth_attachment("spotlight_shadow_atlas")?;

            let atlas_size = if cfg!(target_arch = "wasm32") {
                1024.0
            } else {
                4096.0
            };
            let slot_size = atlas_size / SPOTLIGHT_ATLAS_SLOTS_PER_ROW as f32;

            for slot in &self.spotlight_shadow_slots.clone() {
                let slot_x = slot.slot_index % SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
                let slot_y = slot.slot_index / SPOTLIGHT_ATLAS_SLOTS_PER_ROW;

                let uniforms = ShadowUniforms {
                    light_view_projection: slot.view_projection.into(),
                    _padding: [[0.0; 4]; 4],
                    _padding2: [[0.0; 4]; 4],
                    _padding3: [[0.0; 4]; 4],
                    _padding4: [0.0; 4],
                };

                let slot_uniform_buffer =
                    context
                        .device
                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
                            label: Some("Spotlight Shadow Uniform Buffer"),
                            contents: bytemuck::cast_slice(&[uniforms]),
                            usage: wgpu::BufferUsages::UNIFORM,
                        });

                let slot_uniform_bind_group =
                    context
                        .device
                        .create_bind_group(&wgpu::BindGroupDescriptor {
                            label: Some("Spotlight Shadow Uniform Bind Group"),
                            layout: &self.uniform_bind_group_layout,
                            entries: &[wgpu::BindGroupEntry {
                                binding: 0,
                                resource: slot_uniform_buffer.as_entire_binding(),
                            }],
                        });

                let load_op = if slot.slot_index == 0 {
                    wgpu::LoadOp::Clear(0.0)
                } else {
                    wgpu::LoadOp::Load
                };

                let mut spotlight_pass =
                    context
                        .encoder
                        .begin_render_pass(&wgpu::RenderPassDescriptor {
                            label: Some("Spotlight Shadow Pass"),
                            color_attachments: &[],
                            depth_stencil_attachment: Some(
                                wgpu::RenderPassDepthStencilAttachment {
                                    view: spotlight_atlas_view,
                                    depth_ops: Some(wgpu::Operations {
                                        load: load_op,
                                        store: wgpu::StoreOp::Store,
                                    }),
                                    stencil_ops: None,
                                },
                            ),
                            timestamp_writes: None,
                            occlusion_query_set: None,
                        });

                spotlight_pass.set_viewport(
                    slot_x as f32 * slot_size,
                    slot_y as f32 * slot_size,
                    slot_size,
                    slot_size,
                    0.0,
                    1.0,
                );

                spotlight_pass.set_scissor_rect(
                    (slot_x as f32 * slot_size) as u32,
                    (slot_y as f32 * slot_size) as u32,
                    slot_size as u32,
                    slot_size as u32,
                );

                spotlight_pass.set_pipeline(&self.pipeline);
                spotlight_pass.set_bind_group(0, &slot_uniform_bind_group, &[]);
                spotlight_pass.set_bind_group(1, &self.transform_bind_group, &[]);
                spotlight_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
                spotlight_pass
                    .set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

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

                    if let Some(name) = mesh_name
                        && let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
                            self.meshes.get(name)
                    {
                        spotlight_pass.draw_indexed(
                            index_offset..(index_offset + index_count),
                            vertex_offset as i32,
                            instance_index as u32..(instance_index as u32 + 1),
                        );
                    }
                }

                if !self.skinned_shadow_caster_entities.is_empty() {
                    spotlight_pass.set_pipeline(&self.skinned_pipeline);
                    spotlight_pass.set_bind_group(0, &slot_uniform_bind_group, &[]);
                    spotlight_pass.set_bind_group(1, &self.skinned_bind_group, &[]);
                    spotlight_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
                    spotlight_pass.set_index_buffer(
                        self.skinned_index_buffer.slice(..),
                        wgpu::IndexFormat::Uint32,
                    );

                    for (instance_index, &entity) in
                        self.skinned_shadow_caster_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(&(vertex_offset, _vertex_count, index_offset, index_count)) =
                                self.skinned_meshes.get(name)
                        {
                            spotlight_pass.draw_indexed(
                                index_offset..(index_offset + index_count),
                                vertex_offset as i32,
                                instance_index as u32..(instance_index as u32 + 1),
                            );
                        }
                    }
                }
            }
        }

        if has_point_light_shadows {
            for slot in &self.point_light_shadow_slots {
                for face_index in 0..POINT_SHADOW_NUM_FACES {
                    let uniform_index =
                        slot.slot_index as usize * POINT_SHADOW_NUM_FACES + face_index;
                    let dynamic_offset =
                        (uniform_index * std::mem::size_of::<PointShadowUniforms>()) as u32;

                    let face_view_index =
                        slot.slot_index as usize * POINT_SHADOW_NUM_FACES + face_index;
                    let face_view = &self.point_light_face_views[face_view_index];

                    let mut point_pass =
                        context
                            .encoder
                            .begin_render_pass(&wgpu::RenderPassDescriptor {
                                label: Some(&format!(
                                    "Point Light {} Face {} Shadow Pass",
                                    slot.slot_index, face_index
                                )),
                                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                                    view: face_view,
                                    resolve_target: None,
                                    ops: wgpu::Operations {
                                        load: wgpu::LoadOp::Clear(wgpu::Color {
                                            r: 1.0,
                                            g: 1.0,
                                            b: 1.0,
                                            a: 1.0,
                                        }),
                                        store: wgpu::StoreOp::Store,
                                    },
                                    depth_slice: None,
                                })],
                                depth_stencil_attachment: Some(
                                    wgpu::RenderPassDepthStencilAttachment {
                                        view: &self.point_light_depth_view,
                                        depth_ops: Some(wgpu::Operations {
                                            load: wgpu::LoadOp::Clear(0.0),
                                            store: wgpu::StoreOp::Discard,
                                        }),
                                        stencil_ops: None,
                                    },
                                ),
                                timestamp_writes: None,
                                occlusion_query_set: None,
                            });

                    point_pass.set_viewport(
                        0.0,
                        0.0,
                        POINT_SHADOW_FACE_SIZE as f32,
                        POINT_SHADOW_FACE_SIZE as f32,
                        0.0,
                        1.0,
                    );

                    point_pass.set_pipeline(&self.point_light_pipeline);
                    point_pass.set_bind_group(
                        0,
                        &self.point_light_uniform_bind_group,
                        &[dynamic_offset],
                    );
                    point_pass.set_bind_group(1, &self.transform_bind_group, &[]);
                    point_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
                    point_pass
                        .set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

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

                        if let Some(name) = mesh_name
                            && let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
                                self.meshes.get(name)
                        {
                            point_pass.draw_indexed(
                                index_offset..(index_offset + index_count),
                                vertex_offset as i32,
                                instance_index as u32..(instance_index as u32 + 1),
                            );
                        }
                    }
                }
            }
        }

        Ok(vec![])
    }
}
