use crate::ecs::mesh::components::{SkinnedVertex, Vertex};
use crate::ecs::prefab::resources::mesh_cache_iter;
use crate::ecs::skin::systems::GpuSkinData;
use std::collections::HashMap;
use wgpu::util::DeviceExt;

use super::types::{
    CASCADE_SPLIT_DISTANCES, CascadeData, INITIAL_MAX_SKINS, INITIAL_MAX_TOTAL_BONES,
    MAX_JOINTS_PER_SKIN, MAX_POINT_LIGHT_SHADOWS, NUM_SHADOW_CASCADES, POINT_SHADOW_FACE_SIZE,
    POINT_SHADOW_NUM_FACES, PointLightShadowData, PointLightShadowSlot, PointShadowUniforms,
    ShadowUniforms, SkinnedShadowObjectData, SpotlightShadowData, SpotlightShadowSlot,
};

pub struct ShadowDepthPass {
    pub(super) pipeline: wgpu::RenderPipeline,

    pub(super) uniform_buffer: wgpu::Buffer,
    pub(super) uniform_bind_group_layout: wgpu::BindGroupLayout,

    pub(super) transform_buffer: wgpu::Buffer,
    pub(super) transform_bind_group: wgpu::BindGroup,
    pub(super) transform_bind_group_layout: wgpu::BindGroupLayout,

    pub(super) transform_buffer_size: usize,

    pub(super) vertex_buffer: wgpu::Buffer,
    pub(super) index_buffer: wgpu::Buffer,
    pub(super) vertex_buffer_size: u64,
    pub(super) index_buffer_size: u64,

    pub(super) meshes: HashMap<String, (u32, u32, u32, u32)>,

    pub(super) current_vertex_offset: u32,
    pub(super) current_index_offset: u32,

    pub(super) shadow_caster_entities: Vec<crate::ecs::world::Entity>,

    pub(super) skinned_pipeline: wgpu::RenderPipeline,
    pub(super) skinned_object_buffer: wgpu::Buffer,
    pub(super) skinned_object_buffer_size: usize,
    pub(super) skinned_bind_group: wgpu::BindGroup,
    pub(super) skinned_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) joint_matrices_buffer: wgpu::Buffer,
    pub(super) bone_transforms_buffer: wgpu::Buffer,
    pub(super) inverse_bind_matrices_buffer: wgpu::Buffer,
    pub(super) skin_data_buffer: wgpu::Buffer,
    pub(super) bone_transforms_buffer_size: usize,
    pub(super) inverse_bind_matrices_buffer_size: usize,
    pub(super) joint_matrices_buffer_size: usize,
    pub(super) skin_data_buffer_size: usize,
    pub(super) skinning_compute_pipeline: wgpu::ComputePipeline,
    pub(super) skinning_compute_bind_group: wgpu::BindGroup,
    pub(super) skinning_compute_bind_group_layout: wgpu::BindGroupLayout,

    pub(super) skinned_vertex_buffer: wgpu::Buffer,
    pub(super) skinned_index_buffer: wgpu::Buffer,
    pub(super) skinned_vertex_buffer_size: u64,
    pub(super) skinned_index_buffer_size: u64,
    pub(super) skinned_meshes: HashMap<String, (u32, u32, u32, u32)>,
    pub(super) current_skinned_vertex_offset: u32,
    pub(super) current_skinned_index_offset: u32,
    pub(super) skinned_shadow_caster_entities: Vec<crate::ecs::world::Entity>,
    pub(super) total_joints_to_dispatch: u32,

    pub(super) spotlight_shadow_slots: Vec<SpotlightShadowSlot>,
    pub(super) spotlight_shadow_data: Vec<SpotlightShadowData>,
    pub(super) spotlight_atlas_slot_size: u32,

    pub(super) point_light_pipeline: wgpu::RenderPipeline,
    pub point_light_cubemap: wgpu::Texture,
    pub(super) point_light_cubemap_view: wgpu::TextureView,
    pub(super) point_light_face_views: Vec<wgpu::TextureView>,
    pub point_light_depth_texture: wgpu::Texture,
    pub(super) point_light_depth_view: wgpu::TextureView,
    pub(super) point_light_uniform_buffer: wgpu::Buffer,
    pub(super) point_light_uniform_bind_group: wgpu::BindGroup,
    pub(super) point_light_shadow_slots: Vec<PointLightShadowSlot>,
    pub(super) point_light_shadow_data: Vec<PointLightShadowData>,

    pub(super) cascade_data: CascadeData,
    pub(super) cascade_uniform_buffers: [wgpu::Buffer; NUM_SHADOW_CASCADES],
    pub(super) cascade_uniform_bind_groups: [wgpu::BindGroup; NUM_SHADOW_CASCADES],
}

impl ShadowDepthPass {
    pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
        let shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/shadow_depth.wgsl"));

        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Uniform Buffer"),
            size: std::mem::size_of::<ShadowUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Shadow Uniform Bind Group Layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });

        let initial_cascade_uniforms = ShadowUniforms {
            light_view_projection: [
                [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],
            ],
            _padding: [[0.0; 4]; 4],
            _padding2: [[0.0; 4]; 4],
            _padding3: [[0.0; 4]; 4],
            _padding4: [0.0; 4],
        };
        let cascade_uniform_buffers: [wgpu::Buffer; NUM_SHADOW_CASCADES] =
            std::array::from_fn(|_index| {
                device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                    label: Some("Cascade Uniform Buffer"),
                    contents: bytemuck::cast_slice(&[initial_cascade_uniforms]),
                    usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                })
            });

        let cascade_uniform_bind_groups: [wgpu::BindGroup; NUM_SHADOW_CASCADES] =
            std::array::from_fn(|index| {
                device.create_bind_group(&wgpu::BindGroupDescriptor {
                    label: Some(&format!("Cascade {} Uniform Bind Group", index)),
                    layout: &uniform_bind_group_layout,
                    entries: &[wgpu::BindGroupEntry {
                        binding: 0,
                        resource: cascade_uniform_buffers[index].as_entire_binding(),
                    }],
                })
            });

        let initial_transform_count = 1000;
        let transform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Transform Buffer"),
            size: (std::mem::size_of::<[[f32; 4]; 4]>() * initial_transform_count) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let transform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Shadow Transform Bind Group Layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Storage { read_only: true },
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });

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

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Shadow Pipeline Layout"),
            bind_group_layouts: &[&uniform_bind_group_layout, &transform_bind_group_layout],
            push_constant_ranges: &[],
        });

        let vertex_attributes = wgpu::vertex_attr_array![
            0 => Float32x3,
            1 => Float32x3,
            2 => Float32x2,
        ];

        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Shadow Depth Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vertex_main"),
                buffers: &[wgpu::VertexBufferLayout {
                    array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
                    step_mode: wgpu::VertexStepMode::Vertex,
                    attributes: &vertex_attributes,
                }],
                compilation_options: Default::default(),
            },
            fragment: None,
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::GreaterEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let initial_vertex_count = 10000;
        let initial_index_count = 30000;

        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Vertex Buffer"),
            size: (std::mem::size_of::<Vertex>() * initial_vertex_count) as u64,
            usage: wgpu::BufferUsages::VERTEX
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Index Buffer"),
            size: (std::mem::size_of::<u32>() * initial_index_count) as u64,
            usage: wgpu::BufferUsages::INDEX
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let bone_transforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Bone Transforms Buffer"),
            size: (std::mem::size_of::<[[f32; 4]; 4]>() * INITIAL_MAX_TOTAL_BONES) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let inverse_bind_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Inverse Bind Matrices Buffer"),
            size: (std::mem::size_of::<[[f32; 4]; 4]>() * MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS)
                as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let joint_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Joint Matrices Buffer"),
            size: (std::mem::size_of::<[[f32; 4]; 4]>() * MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS)
                as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let skin_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Skin Data Buffer"),
            size: (std::mem::size_of::<GpuSkinData>() * INITIAL_MAX_SKINS) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let skinning_compute_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/skinning_compute.wgsl"));

        let skinning_compute_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Shadow Skinning Compute Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let initial_joint_matrices_size = MAX_JOINTS_PER_SKIN * INITIAL_MAX_SKINS;

        let skinning_compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Shadow Skinning Compute Bind Group"),
            layout: &skinning_compute_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: bone_transforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: inverse_bind_matrices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: skin_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: joint_matrices_buffer.as_entire_binding(),
                },
            ],
        });

        let skinning_compute_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Shadow Skinning Compute Pipeline Layout"),
                bind_group_layouts: &[&skinning_compute_bind_group_layout],
                push_constant_ranges: &[],
            });

        let skinning_compute_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Shadow Skinning Compute Pipeline"),
                layout: Some(&skinning_compute_pipeline_layout),
                module: &skinning_compute_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let initial_skinned_object_count = 100;
        let skinned_object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Skinned Object Buffer"),
            size: (std::mem::size_of::<SkinnedShadowObjectData>() * initial_skinned_object_count)
                as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let skinned_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Shadow Skinned Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let skinned_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Shadow Skinned Bind Group"),
            layout: &skinned_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: skinned_object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: joint_matrices_buffer.as_entire_binding(),
                },
            ],
        });

        let skinned_shadow_shader = device.create_shader_module(wgpu::include_wgsl!(
            "../../shaders/skinned_shadow_depth.wgsl"
        ));

        let skinned_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Skinned Shadow Pipeline Layout"),
                bind_group_layouts: &[&uniform_bind_group_layout, &skinned_bind_group_layout],
                push_constant_ranges: &[],
            });

        let skinned_vertex_layout = wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<SkinnedVertex>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &[
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 0,
                    format: wgpu::VertexFormat::Float32x3,
                },
                wgpu::VertexAttribute {
                    offset: 12,
                    shader_location: 1,
                    format: wgpu::VertexFormat::Float32x3,
                },
                wgpu::VertexAttribute {
                    offset: 24,
                    shader_location: 2,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: 32,
                    shader_location: 3,
                    format: wgpu::VertexFormat::Float32x4,
                },
                wgpu::VertexAttribute {
                    offset: 48,
                    shader_location: 4,
                    format: wgpu::VertexFormat::Uint32x4,
                },
                wgpu::VertexAttribute {
                    offset: 64,
                    shader_location: 5,
                    format: wgpu::VertexFormat::Float32x4,
                },
            ],
        };

        let skinned_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Skinned Shadow Depth Pipeline"),
            layout: Some(&skinned_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &skinned_shadow_shader,
                entry_point: Some("vs_main"),
                buffers: &[skinned_vertex_layout],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &skinned_shadow_shader,
                entry_point: Some("fs_main"),
                targets: &[],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::GreaterEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let initial_skinned_vertex_count = 10000;
        let initial_skinned_index_count = 30000;

        let skinned_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Skinned Vertex Buffer"),
            size: (std::mem::size_of::<SkinnedVertex>() * initial_skinned_vertex_count) as u64,
            usage: wgpu::BufferUsages::VERTEX
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let skinned_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Shadow Skinned Index Buffer"),
            size: (std::mem::size_of::<u32>() * initial_skinned_index_count) as u64,
            usage: wgpu::BufferUsages::INDEX
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let point_light_cubemap = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Point Light Shadow Cubemap Array"),
            size: wgpu::Extent3d {
                width: POINT_SHADOW_FACE_SIZE,
                height: POINT_SHADOW_FACE_SIZE,
                depth_or_array_layers: (POINT_SHADOW_NUM_FACES * MAX_POINT_LIGHT_SHADOWS) as u32,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R32Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });

        let point_light_cubemap_view =
            point_light_cubemap.create_view(&wgpu::TextureViewDescriptor {
                label: Some("Point Light Shadow Cubemap Array View"),
                format: Some(wgpu::TextureFormat::R32Float),
                dimension: Some(wgpu::TextureViewDimension::CubeArray),
                aspect: wgpu::TextureAspect::All,
                base_mip_level: 0,
                mip_level_count: Some(1),
                base_array_layer: 0,
                array_layer_count: Some((POINT_SHADOW_NUM_FACES * MAX_POINT_LIGHT_SHADOWS) as u32),
                usage: Some(wgpu::TextureUsages::TEXTURE_BINDING),
            });

        let mut point_light_face_views =
            Vec::with_capacity(POINT_SHADOW_NUM_FACES * MAX_POINT_LIGHT_SHADOWS);
        for light_index in 0..MAX_POINT_LIGHT_SHADOWS {
            for face_index in 0..POINT_SHADOW_NUM_FACES {
                let layer = (light_index * POINT_SHADOW_NUM_FACES + face_index) as u32;
                let view = point_light_cubemap.create_view(&wgpu::TextureViewDescriptor {
                    label: Some(&format!(
                        "Point Light {} Face {} View",
                        light_index, face_index
                    )),
                    format: Some(wgpu::TextureFormat::R32Float),
                    dimension: Some(wgpu::TextureViewDimension::D2),
                    aspect: wgpu::TextureAspect::All,
                    base_mip_level: 0,
                    mip_level_count: Some(1),
                    base_array_layer: layer,
                    array_layer_count: Some(1),
                    usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT),
                });
                point_light_face_views.push(view);
            }
        }

        let point_light_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Point Light Shadow Depth Texture"),
            size: wgpu::Extent3d {
                width: POINT_SHADOW_FACE_SIZE,
                height: POINT_SHADOW_FACE_SIZE,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth32Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

        let point_light_depth_view =
            point_light_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let point_uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Point Light Uniform Bind Group Layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: true,
                        min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<
                            PointShadowUniforms,
                        >() as u64),
                    },
                    count: None,
                }],
            });

        let total_point_uniforms = MAX_POINT_LIGHT_SHADOWS * POINT_SHADOW_NUM_FACES;
        let point_light_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Point Light Shadow Uniform Buffer"),
            size: (std::mem::size_of::<PointShadowUniforms>() * total_point_uniforms) as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let point_light_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Point Light Shadow Uniform Bind Group"),
            layout: &point_uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
                    buffer: &point_light_uniform_buffer,
                    offset: 0,
                    size: wgpu::BufferSize::new(std::mem::size_of::<PointShadowUniforms>() as u64),
                }),
            }],
        });

        let point_shadow_shader = device
            .create_shader_module(wgpu::include_wgsl!("../../shaders/point_shadow_depth.wgsl"));

        let point_light_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Point Light Shadow Pipeline Layout"),
                bind_group_layouts: &[
                    &point_uniform_bind_group_layout,
                    &transform_bind_group_layout,
                ],
                push_constant_ranges: &[],
            });

        let point_light_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Point Light Shadow Pipeline"),
            layout: Some(&point_light_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &point_shadow_shader,
                entry_point: Some("vs_main"),
                buffers: &[wgpu::VertexBufferLayout {
                    array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
                    step_mode: wgpu::VertexStepMode::Vertex,
                    attributes: &vertex_attributes,
                }],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &point_shadow_shader,
                entry_point: Some("fs_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: wgpu::TextureFormat::R32Float,
                    blend: None,
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Cw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::GreaterEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let mut pass = Self {
            pipeline,
            uniform_buffer,
            uniform_bind_group_layout,
            transform_buffer,
            transform_bind_group,
            transform_bind_group_layout,
            transform_buffer_size: initial_transform_count,
            vertex_buffer,
            index_buffer,
            vertex_buffer_size: (std::mem::size_of::<Vertex>() * initial_vertex_count) as u64,
            index_buffer_size: (std::mem::size_of::<u32>() * initial_index_count) as u64,
            meshes: HashMap::new(),
            current_vertex_offset: 0,
            current_index_offset: 0,
            shadow_caster_entities: Vec::new(),
            skinned_pipeline,
            skinned_object_buffer,
            skinned_object_buffer_size: initial_skinned_object_count,
            skinned_bind_group,
            skinned_bind_group_layout,
            joint_matrices_buffer,
            bone_transforms_buffer,
            inverse_bind_matrices_buffer,
            skin_data_buffer,
            bone_transforms_buffer_size: INITIAL_MAX_TOTAL_BONES,
            inverse_bind_matrices_buffer_size: initial_joint_matrices_size,
            joint_matrices_buffer_size: initial_joint_matrices_size,
            skin_data_buffer_size: INITIAL_MAX_SKINS,
            skinning_compute_pipeline,
            skinning_compute_bind_group,
            skinning_compute_bind_group_layout,
            skinned_vertex_buffer,
            skinned_index_buffer,
            skinned_vertex_buffer_size: (std::mem::size_of::<SkinnedVertex>()
                * initial_skinned_vertex_count) as u64,
            skinned_index_buffer_size: (std::mem::size_of::<u32>() * initial_skinned_index_count)
                as u64,
            skinned_meshes: HashMap::new(),
            current_skinned_vertex_offset: 0,
            current_skinned_index_offset: 0,
            skinned_shadow_caster_entities: Vec::new(),
            total_joints_to_dispatch: 0,
            spotlight_shadow_slots: Vec::new(),
            spotlight_shadow_data: Vec::new(),
            spotlight_atlas_slot_size: 0,
            point_light_pipeline,
            point_light_cubemap,
            point_light_cubemap_view,
            point_light_face_views,
            point_light_depth_texture,
            point_light_depth_view,
            point_light_uniform_buffer,
            point_light_uniform_bind_group,
            point_light_shadow_slots: Vec::new(),
            point_light_shadow_data: Vec::new(),
            cascade_data: CascadeData {
                view_projections: [[[0.0; 4]; 4]; NUM_SHADOW_CASCADES],
                split_distances: CASCADE_SPLIT_DISTANCES,
                atlas_offsets: [[0.0, 0.0], [0.5, 0.0], [0.0, 0.5], [0.5, 0.5]],
                atlas_scale: [0.5, 0.5],
                _padding: [0.0, 0.0],
            },
            cascade_uniform_buffers,
            cascade_uniform_bind_groups,
        };

        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,
        };
        let cube_mesh = create_cube_mesh();
        pass.add_mesh(
            device,
            queue,
            "Cube".to_string(),
            &cube_mesh.vertices,
            &cube_mesh.indices,
        );
        let sphere_mesh = create_sphere_mesh(1.0, 16);
        pass.add_mesh(
            device,
            queue,
            "Sphere".to_string(),
            &sphere_mesh.vertices,
            &sphere_mesh.indices,
        );
        let torus_mesh = create_torus_mesh(1.0, 0.3, 16, 16);
        pass.add_mesh(
            device,
            queue,
            "Torus".to_string(),
            &torus_mesh.vertices,
            &torus_mesh.indices,
        );
        let plane_mesh = create_plane_mesh(2.0);
        pass.add_mesh(
            device,
            queue,
            "Plane".to_string(),
            &plane_mesh.vertices,
            &plane_mesh.indices,
        );
        let subdivided_plane_mesh = create_subdivided_plane_mesh(2.0, 20);
        pass.add_mesh(
            device,
            queue,
            "SubdividedPlane".to_string(),
            &subdivided_plane_mesh.vertices,
            &subdivided_plane_mesh.indices,
        );
        let cylinder_mesh = create_cylinder_mesh(0.5, 1.0, 16);
        pass.add_mesh(
            device,
            queue,
            "Cylinder".to_string(),
            &cylinder_mesh.vertices,
            &cylinder_mesh.indices,
        );
        let cone_mesh = create_cone_mesh(0.5, 1.0, 16);
        pass.add_mesh(
            device,
            queue,
            "Cone".to_string(),
            &cone_mesh.vertices,
            &cone_mesh.indices,
        );

        pass
    }

    pub fn add_mesh(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        name: String,
        vertices: &[Vertex],
        indices: &[u32],
    ) {
        let vertex_size = std::mem::size_of_val(vertices) as u64;
        let index_size = std::mem::size_of_val(indices) as u64;

        let required_vertex_size = (self.current_vertex_offset as u64
            * std::mem::size_of::<Vertex>() as u64)
            + vertex_size;
        let required_index_size =
            (self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64) + index_size;

        if required_vertex_size > self.vertex_buffer_size {
            let new_size = (required_vertex_size * 2).max(self.vertex_buffer_size * 2);
            let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Shadow Vertex Buffer (Resized)"),
                size: new_size,
                usage: wgpu::BufferUsages::VERTEX
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC,
                mapped_at_creation: false,
            });

            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Shadow Vertex Buffer Copy"),
            });
            encoder.copy_buffer_to_buffer(
                &self.vertex_buffer,
                0,
                &new_buffer,
                0,
                self.vertex_buffer_size,
            );
            queue.submit(Some(encoder.finish()));

            self.vertex_buffer = new_buffer;
            self.vertex_buffer_size = new_size;
        }

        if required_index_size > self.index_buffer_size {
            let new_size = (required_index_size * 2).max(self.index_buffer_size * 2);
            let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Shadow Index Buffer (Resized)"),
                size: new_size,
                usage: wgpu::BufferUsages::INDEX
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC,
                mapped_at_creation: false,
            });

            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Shadow Index Buffer Copy"),
            });
            encoder.copy_buffer_to_buffer(
                &self.index_buffer,
                0,
                &new_buffer,
                0,
                self.index_buffer_size,
            );
            queue.submit(Some(encoder.finish()));

            self.index_buffer = new_buffer;
            self.index_buffer_size = new_size;
        }

        queue.write_buffer(
            &self.vertex_buffer,
            self.current_vertex_offset as u64 * std::mem::size_of::<Vertex>() as u64,
            bytemuck::cast_slice(vertices),
        );

        queue.write_buffer(
            &self.index_buffer,
            self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64,
            bytemuck::cast_slice(indices),
        );

        self.meshes.insert(
            name,
            (
                self.current_vertex_offset,
                vertices.len() as u32,
                self.current_index_offset,
                indices.len() as u32,
            ),
        );

        self.current_vertex_offset += vertices.len() as u32;
        self.current_index_offset += indices.len() as u32;
    }

    pub fn sync_meshes(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        mesh_cache: &crate::ecs::prefab::resources::MeshCache,
    ) {
        for (name, mesh) in mesh_cache_iter(mesh_cache) {
            if mesh.skin_data.is_some() {
                continue;
            }

            if !self.meshes.contains_key(name.as_str()) {
                let vertex_size = (mesh.vertices.len() * std::mem::size_of::<Vertex>()) as u64;
                let index_size = (mesh.indices.len() * std::mem::size_of::<u32>()) as u64;

                let required_vertex_size = (self.current_vertex_offset as u64
                    * std::mem::size_of::<Vertex>() as u64)
                    + vertex_size;
                let required_index_size = (self.current_index_offset as u64
                    * std::mem::size_of::<u32>() as u64)
                    + index_size;

                if required_vertex_size > self.vertex_buffer_size {
                    let new_size = (required_vertex_size * 2).max(self.vertex_buffer_size * 2);
                    let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Shadow Vertex Buffer (Resized)"),
                        size: new_size,
                        usage: wgpu::BufferUsages::VERTEX
                            | wgpu::BufferUsages::COPY_DST
                            | wgpu::BufferUsages::COPY_SRC,
                        mapped_at_creation: false,
                    });

                    let mut encoder =
                        device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                            label: Some("Shadow Vertex Buffer Copy"),
                        });
                    encoder.copy_buffer_to_buffer(
                        &self.vertex_buffer,
                        0,
                        &new_buffer,
                        0,
                        self.vertex_buffer_size,
                    );
                    queue.submit(Some(encoder.finish()));

                    self.vertex_buffer = new_buffer;
                    self.vertex_buffer_size = new_size;
                }

                if required_index_size > self.index_buffer_size {
                    let new_size = (required_index_size * 2).max(self.index_buffer_size * 2);
                    let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Shadow Index Buffer (Resized)"),
                        size: new_size,
                        usage: wgpu::BufferUsages::INDEX
                            | wgpu::BufferUsages::COPY_DST
                            | wgpu::BufferUsages::COPY_SRC,
                        mapped_at_creation: false,
                    });

                    let mut encoder =
                        device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                            label: Some("Shadow Index Buffer Copy"),
                        });
                    encoder.copy_buffer_to_buffer(
                        &self.index_buffer,
                        0,
                        &new_buffer,
                        0,
                        self.index_buffer_size,
                    );
                    queue.submit(Some(encoder.finish()));

                    self.index_buffer = new_buffer;
                    self.index_buffer_size = new_size;
                }

                queue.write_buffer(
                    &self.vertex_buffer,
                    self.current_vertex_offset as u64 * std::mem::size_of::<Vertex>() as u64,
                    bytemuck::cast_slice(&mesh.vertices),
                );

                queue.write_buffer(
                    &self.index_buffer,
                    self.current_index_offset as u64 * std::mem::size_of::<u32>() as u64,
                    bytemuck::cast_slice(&mesh.indices),
                );

                self.meshes.insert(
                    name.clone(),
                    (
                        self.current_vertex_offset,
                        mesh.vertices.len() as u32,
                        self.current_index_offset,
                        mesh.indices.len() as u32,
                    ),
                );

                self.current_vertex_offset += mesh.vertices.len() as u32;
                self.current_index_offset += mesh.indices.len() as u32;
            }
        }
    }

    pub fn sync_skinned_meshes(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        mesh_cache: &crate::ecs::prefab::resources::MeshCache,
    ) {
        for (name, mesh) in mesh_cache_iter(mesh_cache) {
            if mesh.skin_data.is_none() {
                continue;
            }

            if self.skinned_meshes.contains_key(name.as_str()) {
                continue;
            }

            let skin_data = mesh.skin_data.as_ref().unwrap();
            let skinned_vertices = &skin_data.skinned_vertices;

            let vertex_size =
                (skinned_vertices.len() * std::mem::size_of::<SkinnedVertex>()) as u64;
            let index_size = (mesh.indices.len() * std::mem::size_of::<u32>()) as u64;

            let required_vertex_size = (self.current_skinned_vertex_offset as u64
                * std::mem::size_of::<SkinnedVertex>() as u64)
                + vertex_size;
            let required_index_size = (self.current_skinned_index_offset as u64
                * std::mem::size_of::<u32>() as u64)
                + index_size;

            if required_vertex_size > self.skinned_vertex_buffer_size {
                let new_size = (required_vertex_size * 2).max(self.skinned_vertex_buffer_size * 2);
                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Shadow Skinned Vertex Buffer (Resized)"),
                    size: new_size,
                    usage: wgpu::BufferUsages::VERTEX
                        | wgpu::BufferUsages::COPY_DST
                        | wgpu::BufferUsages::COPY_SRC,
                    mapped_at_creation: false,
                });

                let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                    label: Some("Shadow Skinned Vertex Buffer Copy"),
                });
                encoder.copy_buffer_to_buffer(
                    &self.skinned_vertex_buffer,
                    0,
                    &new_buffer,
                    0,
                    self.skinned_vertex_buffer_size,
                );
                queue.submit(Some(encoder.finish()));

                self.skinned_vertex_buffer = new_buffer;
                self.skinned_vertex_buffer_size = new_size;
            }

            if required_index_size > self.skinned_index_buffer_size {
                let new_size = (required_index_size * 2).max(self.skinned_index_buffer_size * 2);
                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Shadow Skinned Index Buffer (Resized)"),
                    size: new_size,
                    usage: wgpu::BufferUsages::INDEX
                        | wgpu::BufferUsages::COPY_DST
                        | wgpu::BufferUsages::COPY_SRC,
                    mapped_at_creation: false,
                });

                let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                    label: Some("Shadow Skinned Index Buffer Copy"),
                });
                encoder.copy_buffer_to_buffer(
                    &self.skinned_index_buffer,
                    0,
                    &new_buffer,
                    0,
                    self.skinned_index_buffer_size,
                );
                queue.submit(Some(encoder.finish()));

                self.skinned_index_buffer = new_buffer;
                self.skinned_index_buffer_size = new_size;
            }

            queue.write_buffer(
                &self.skinned_vertex_buffer,
                self.current_skinned_vertex_offset as u64
                    * std::mem::size_of::<SkinnedVertex>() as u64,
                bytemuck::cast_slice(skinned_vertices),
            );

            queue.write_buffer(
                &self.skinned_index_buffer,
                self.current_skinned_index_offset as u64 * std::mem::size_of::<u32>() as u64,
                bytemuck::cast_slice(&mesh.indices),
            );

            self.skinned_meshes.insert(
                name.clone(),
                (
                    self.current_skinned_vertex_offset,
                    skinned_vertices.len() as u32,
                    self.current_skinned_index_offset,
                    mesh.indices.len() as u32,
                ),
            );

            self.current_skinned_vertex_offset += skinned_vertices.len() as u32;
            self.current_skinned_index_offset += mesh.indices.len() as u32;
        }
    }

    pub fn rebuild_skinned_bind_group(&mut self, device: &wgpu::Device) {
        self.skinned_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Shadow Skinned Bind Group"),
            layout: &self.skinned_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.skinned_object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: self.joint_matrices_buffer.as_entire_binding(),
                },
            ],
        });
    }

    pub fn rebuild_skinning_compute_bind_group(&mut self, device: &wgpu::Device) {
        self.skinning_compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Shadow Skinning Compute Bind Group"),
            layout: &self.skinning_compute_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.bone_transforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: self.inverse_bind_matrices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: self.skin_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: self.joint_matrices_buffer.as_entire_binding(),
                },
            ],
        });
    }

    pub fn spotlight_shadow_slots(&self) -> &[SpotlightShadowSlot] {
        &self.spotlight_shadow_slots
    }

    pub fn spotlight_shadow_data(&self) -> &[SpotlightShadowData] {
        &self.spotlight_shadow_data
    }

    pub fn cascade_data(&self) -> &CascadeData {
        &self.cascade_data
    }

    pub fn point_light_shadow_slots(&self) -> &[PointLightShadowSlot] {
        &self.point_light_shadow_slots
    }

    pub fn point_light_shadow_data(&self) -> &[PointLightShadowData] {
        &self.point_light_shadow_data
    }

    pub fn point_light_cubemap_view(&self) -> &wgpu::TextureView {
        &self.point_light_cubemap_view
    }
}
