use crate::ecs::particles::components::{ColorGradient, EmitterShape, EmitterType};
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use freecs::Entity;
use nalgebra_glm::{Mat4, Vec3};
use std::collections::HashMap;
use wgpu::util::DeviceExt;

const MAX_PARTICLES: u32 = 1_000_000;
const MAX_EMITTERS: u32 = 512;
const WORKGROUP_SIZE: u32 = 256;

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuParticle {
    position: [f32; 4],
    velocity: [f32; 4],
    color: [f32; 4],
    size_lifetime: [f32; 4],
    emitter_data: [f32; 4],
    physics: [f32; 4],
    color_start: [f32; 4],
    color_end: [f32; 4],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuEmitterData {
    position: [f32; 4],
    direction: [f32; 4],
    velocity_range: [f32; 4],
    lifetime_range: [f32; 4],
    size_range: [f32; 4],
    gravity: [f32; 4],
    color_gradient: [[f32; 4]; 16],
    gradient_count: u32,
    spawn_count: u32,
    emitter_id: u32,
    shape_type: u32,
    shape_params: [f32; 4],
    turbulence: [f32; 4],
    emissive_strength: f32,
    drag: f32,
    emitter_type: u32,
    texture_index: u32,
    size_curve: [[f32; 4]; 8],
    size_curve_count: u32,
    _size_curve_pad: [u32; 3],
    opacity_curve: [[f32; 4]; 8],
    opacity_curve_count: u32,
    _opacity_curve_pad: [u32; 3],
}

impl Default for GpuEmitterData {
    fn default() -> Self {
        Self {
            position: [0.0; 4],
            direction: [0.0, 1.0, 0.0, 0.0],
            velocity_range: [1.0, 3.0, 0.3, 0.0],
            lifetime_range: [1.0, 2.0, 0.0, 0.0],
            size_range: [0.1, 0.0, 0.0, 0.0],
            gravity: [0.0, -9.81, 0.0, 0.0],
            color_gradient: [[0.0; 4]; 16],
            gradient_count: 0,
            spawn_count: 0,
            emitter_id: 0,
            shape_type: 0,
            shape_params: [0.0; 4],
            turbulence: [0.0; 4],
            emissive_strength: 1.0,
            drag: 0.1,
            emitter_type: 0,
            texture_index: 0,
            size_curve: [[0.0; 4]; 8],
            size_curve_count: 0,
            _size_curve_pad: [0; 3],
            opacity_curve: [[0.0; 4]; 8],
            opacity_curve_count: 0,
            _opacity_curve_pad: [0; 3],
        }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SimParams {
    delta_time: f32,
    time: f32,
    max_particles: u32,
    _padding: u32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct DrawIndirect {
    vertex_count: u32,
    instance_count: u32,
    first_vertex: u32,
    first_instance: u32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniforms {
    view: [[f32; 4]; 4],
    projection: [[f32; 4]; 4],
    view_projection: [[f32; 4]; 4],
    camera_position: [f32; 4],
    camera_right: [f32; 4],
    camera_up: [f32; 4],
}

const PARTICLE_TEXTURE_SIZE: u32 = 512;
const MAX_PARTICLE_TEXTURES: u32 = 64;

pub struct ParticlePass {
    _particle_buffer: wgpu::Buffer,
    emitter_buffer: wgpu::Buffer,
    params_buffer: wgpu::Buffer,
    _free_indices_buffer: wgpu::Buffer,
    _free_count_buffer: wgpu::Buffer,
    _alive_indices_buffer: wgpu::Buffer,
    _alive_count_buffer: wgpu::Buffer,
    draw_indirect_buffer: wgpu::Buffer,
    camera_buffer: wgpu::Buffer,
    update_pipeline: wgpu::ComputePipeline,
    spawn_pipeline: wgpu::ComputePipeline,
    reset_pipeline: wgpu::ComputePipeline,
    render_pipeline: wgpu::RenderPipeline,
    render_pipeline_additive: wgpu::RenderPipeline,
    _compute_bind_group_layout: wgpu::BindGroupLayout,
    compute_bind_group: wgpu::BindGroup,
    _render_bind_group_layout: wgpu::BindGroupLayout,
    render_bind_group: wgpu::BindGroup,
    texture_bind_group_layout: wgpu::BindGroupLayout,
    texture_bind_group: wgpu::BindGroup,
    texture_array: wgpu::Texture,
    texture_array_view: wgpu::TextureView,
    texture_sampler: wgpu::Sampler,
    max_particles: u32,
    _initialized: bool,
    emitter_data: Vec<GpuEmitterData>,
    time: f32,
    use_additive_blending: bool,
    emitter_spawn_offsets: HashMap<Entity, f32>,
    loaded_texture_count: usize,
}

impl ParticlePass {
    pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
        let max_particles = MAX_PARTICLES;

        let particle_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Particle Buffer"),
            size: (std::mem::size_of::<GpuParticle>() * max_particles as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let emitter_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Emitter Buffer"),
            size: (std::mem::size_of::<GpuEmitterData>() * MAX_EMITTERS as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let params_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Particle Sim Params Buffer"),
            size: std::mem::size_of::<SimParams>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let mut free_indices: Vec<u32> = (0..max_particles).collect();
        free_indices.reverse();

        let free_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Free Indices Buffer"),
            contents: bytemuck::cast_slice(&free_indices),
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
        });

        let free_count_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Free Count Buffer"),
            contents: bytemuck::cast_slice(&[max_particles]),
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
        });

        let alive_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Alive Indices Buffer"),
            size: (std::mem::size_of::<u32>() * max_particles as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let alive_count_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Alive Count Buffer"),
            contents: bytemuck::cast_slice(&[0u32]),
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
        });

        let draw_indirect_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Draw Indirect Buffer"),
            contents: bytemuck::cast_slice(&[DrawIndirect {
                vertex_count: 6,
                instance_count: 0,
                first_vertex: 0,
                first_instance: 0,
            }]),
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::COPY_DST,
        });

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

        let compute_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Particle Compute Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        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,
                    },
                    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::Uniform,
                            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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 4,
                        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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 6,
                        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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 7,
                        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 compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Particle Compute Bind Group"),
            layout: &compute_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: particle_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: emitter_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: params_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: free_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: free_count_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: alive_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: alive_count_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: draw_indirect_buffer.as_entire_binding(),
                },
            ],
        });

        let render_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Particle Render 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,
                    },
                    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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        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 render_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Particle Render Bind Group"),
            layout: &render_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: camera_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: particle_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: alive_indices_buffer.as_entire_binding(),
                },
            ],
        });

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Particle Texture Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2Array,
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });

        let texture_array = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Particle Texture Array"),
            size: wgpu::Extent3d {
                width: PARTICLE_TEXTURE_SIZE,
                height: PARTICLE_TEXTURE_SIZE,
                depth_or_array_layers: MAX_PARTICLE_TEXTURES,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba8UnormSrgb,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        let texture_array_view = texture_array.create_view(&wgpu::TextureViewDescriptor::default());
        let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            ..Default::default()
        });

        let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Particle Texture Bind Group"),
            layout: &texture_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&texture_array_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&texture_sampler),
                },
            ],
        });

        let compute_shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "Particle Compute Shader",
            include_str!("../../shaders/particle_update.wgsl"),
        );

        let render_shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "Particle Render Shader",
            include_str!("../../shaders/particle_render.wgsl"),
        );

        let compute_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Particle Compute Pipeline Layout"),
                bind_group_layouts: &[Some(&compute_bind_group_layout)],
                immediate_size: 0,
            });

        let update_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Particle Update Pipeline"),
            layout: Some(&compute_pipeline_layout),
            module: &compute_shader,
            entry_point: Some("update"),
            compilation_options: Default::default(),
            cache: None,
        });

        let spawn_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Particle Spawn Pipeline"),
            layout: Some(&compute_pipeline_layout),
            module: &compute_shader,
            entry_point: Some("spawn"),
            compilation_options: Default::default(),
            cache: None,
        });

        let reset_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Particle Reset Pipeline"),
            layout: Some(&compute_pipeline_layout),
            module: &compute_shader,
            entry_point: Some("reset_counters"),
            compilation_options: Default::default(),
            cache: None,
        });

        let render_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Particle Render Pipeline Layout"),
                bind_group_layouts: &[
                    Some(&render_bind_group_layout),
                    Some(&texture_bind_group_layout),
                ],
                immediate_size: 0,
            });

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Particle Render Pipeline"),
            layout: Some(&render_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &render_shader,
                entry_point: Some("vs_main"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &render_shader,
                entry_point: Some("fs_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: Some(false),
                depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview_mask: None,
            cache: None,
        });

        let render_pipeline_additive =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Particle Render Pipeline Additive"),
                layout: Some(&render_pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &render_shader,
                    entry_point: Some("vs_main"),
                    buffers: &[],
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &render_shader,
                    entry_point: Some("fs_main_additive"),
                    targets: &[Some(wgpu::ColorTargetState {
                        format: color_format,
                        blend: Some(wgpu::BlendState {
                            color: wgpu::BlendComponent {
                                src_factor: wgpu::BlendFactor::SrcAlpha,
                                dst_factor: wgpu::BlendFactor::One,
                                operation: wgpu::BlendOperation::Add,
                            },
                            alpha: wgpu::BlendComponent {
                                src_factor: wgpu::BlendFactor::One,
                                dst_factor: wgpu::BlendFactor::One,
                                operation: wgpu::BlendOperation::Add,
                            },
                        }),
                        write_mask: wgpu::ColorWrites::ALL,
                    })],
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: None,
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: wgpu::TextureFormat::Depth32Float,
                    depth_write_enabled: Some(false),
                    depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState::default(),
                multiview_mask: None,
                cache: None,
            });

        Self {
            _particle_buffer: particle_buffer,
            emitter_buffer,
            params_buffer,
            _free_indices_buffer: free_indices_buffer,
            _free_count_buffer: free_count_buffer,
            _alive_indices_buffer: alive_indices_buffer,
            _alive_count_buffer: alive_count_buffer,
            draw_indirect_buffer,
            camera_buffer,
            update_pipeline,
            spawn_pipeline,
            reset_pipeline,
            render_pipeline,
            render_pipeline_additive,
            _compute_bind_group_layout: compute_bind_group_layout,
            compute_bind_group,
            _render_bind_group_layout: render_bind_group_layout,
            render_bind_group,
            texture_bind_group_layout,
            texture_bind_group,
            texture_array,
            texture_array_view,
            texture_sampler,
            max_particles,
            _initialized: true,
            emitter_data: Vec::new(),
            time: 0.0,
            use_additive_blending: true,
            emitter_spawn_offsets: HashMap::new(),
            loaded_texture_count: 0,
        }
    }

    pub fn set_additive_blending(&mut self, enabled: bool) {
        self.use_additive_blending = enabled;
    }

    fn convert_gradient_to_gpu(gradient: &ColorGradient) -> [[f32; 4]; 16] {
        let mut gpu_gradient = [[0.0f32; 4]; 16];
        for (index, (time, color)) in gradient.colors.iter().take(8).enumerate() {
            gpu_gradient[index * 2] = [*time, color.x, color.y, color.z];
            gpu_gradient[index * 2 + 1] = [color.w, 0.0, 0.0, 0.0];
        }
        gpu_gradient
    }

    fn convert_value_curve_to_gpu(curve: &[(f32, f32)]) -> ([[f32; 4]; 8], u32) {
        let mut packed = [[0.0f32; 4]; 8];
        let count = curve.len().min(16) as u32;
        for (index, (time, value)) in curve.iter().take(16).enumerate() {
            let slot = &mut packed[index / 2];
            let lane = (index % 2) * 2;
            slot[lane] = *time;
            slot[lane + 1] = *value;
        }
        (packed, count)
    }

    fn update_emitters(&mut self, world: &World) {
        self.emitter_data.clear();

        let emitter_entities: Vec<_> = world
            .core
            .query_entities(crate::ecs::PARTICLE_EMITTER)
            .collect();

        for (emitter_index, entity) in emitter_entities.iter().enumerate() {
            if self.emitter_data.len() >= MAX_EMITTERS as usize {
                break;
            }

            if let Some(emitter) = world.core.get_particle_emitter(*entity) {
                if !emitter.enabled {
                    self.emitter_spawn_offsets.remove(entity);
                    continue;
                }

                if emitter.one_shot && emitter.has_fired {
                    let offset = self
                        .emitter_spawn_offsets
                        .get(entity)
                        .copied()
                        .unwrap_or(0.0);
                    if offset >= emitter.accumulated_spawn {
                        continue;
                    }
                }

                let offset = self
                    .emitter_spawn_offsets
                    .get(entity)
                    .copied()
                    .unwrap_or(0.0);
                let spawn_count = ((emitter.accumulated_spawn - offset).max(0.0)) as u32;

                if spawn_count > 0 {
                    self.emitter_spawn_offsets
                        .insert(*entity, offset + spawn_count as f32);
                }

                if spawn_count == 0 {
                    continue;
                }

                let (shape_type, shape_params) = match emitter.shape {
                    EmitterShape::Point => (0u32, [0.0f32; 4]),
                    EmitterShape::Sphere { radius } => (1u32, [radius, 0.0, 0.0, 0.0]),
                    EmitterShape::Cone { angle, height } => (2u32, [angle, height, 0.0, 0.0]),
                    EmitterShape::Box { half_extents } => {
                        (3u32, [half_extents.x, half_extents.y, half_extents.z, 0.0])
                    }
                };

                let emitter_type_id = match emitter.emitter_type {
                    EmitterType::Firework => 0u32,
                    EmitterType::Fire => 1u32,
                    EmitterType::Smoke => 2u32,
                    EmitterType::Sparks => 3u32,
                    EmitterType::Trail => 4u32,
                };

                let gpu_emitter = GpuEmitterData {
                    position: [
                        emitter.position.x,
                        emitter.position.y,
                        emitter.position.z,
                        1.0,
                    ],
                    direction: [
                        emitter.direction.x,
                        emitter.direction.y,
                        emitter.direction.z,
                        0.0,
                    ],
                    velocity_range: [
                        emitter.initial_velocity_min,
                        emitter.initial_velocity_max,
                        emitter.velocity_spread,
                        0.0,
                    ],
                    lifetime_range: [
                        emitter.particle_lifetime_min,
                        emitter.particle_lifetime_max,
                        0.0,
                        0.0,
                    ],
                    size_range: [emitter.size_start, emitter.size_end, 0.0, 0.0],
                    gravity: [emitter.gravity.x, emitter.gravity.y, emitter.gravity.z, 0.0],
                    color_gradient: Self::convert_gradient_to_gpu(&emitter.color_gradient),
                    gradient_count: emitter.color_gradient.color_count(),
                    spawn_count,
                    emitter_id: emitter_index as u32,
                    shape_type,
                    shape_params,
                    turbulence: [
                        emitter.turbulence_strength,
                        emitter.turbulence_frequency,
                        0.0,
                        0.0,
                    ],
                    emissive_strength: emitter.emissive_strength,
                    drag: emitter.drag,
                    emitter_type: emitter_type_id,
                    texture_index: emitter.texture_index,
                    size_curve: Self::convert_value_curve_to_gpu(&emitter.size_curve).0,
                    size_curve_count: Self::convert_value_curve_to_gpu(&emitter.size_curve).1,
                    _size_curve_pad: [0; 3],
                    opacity_curve: Self::convert_value_curve_to_gpu(&emitter.opacity_curve).0,
                    opacity_curve_count: Self::convert_value_curve_to_gpu(&emitter.opacity_curve).1,
                    _opacity_curve_pad: [0; 3],
                };

                self.emitter_data.push(gpu_emitter);
            }
        }
    }
}

impl PassNode<World> for ParticlePass {
    fn name(&self) -> &str {
        "particle_pass"
    }

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

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

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

    fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
        let pending = &world.resources.loading.pending_particle_textures;
        if pending.len() > self.loaded_texture_count {
            for upload in &pending[self.loaded_texture_count..] {
                if upload.slot == 0 || upload.slot >= MAX_PARTICLE_TEXTURES {
                    continue;
                }
                let layer = upload.slot - 1;
                let expected_size = (PARTICLE_TEXTURE_SIZE * PARTICLE_TEXTURE_SIZE * 4) as usize;
                if upload.rgba_data.len() == expected_size
                    && upload.width == PARTICLE_TEXTURE_SIZE
                    && upload.height == PARTICLE_TEXTURE_SIZE
                {
                    queue.write_texture(
                        wgpu::TexelCopyTextureInfo {
                            texture: &self.texture_array,
                            mip_level: 0,
                            origin: wgpu::Origin3d {
                                x: 0,
                                y: 0,
                                z: layer,
                            },
                            aspect: wgpu::TextureAspect::All,
                        },
                        &upload.rgba_data,
                        wgpu::TexelCopyBufferLayout {
                            offset: 0,
                            bytes_per_row: Some(PARTICLE_TEXTURE_SIZE * 4),
                            rows_per_image: Some(PARTICLE_TEXTURE_SIZE),
                        },
                        wgpu::Extent3d {
                            width: PARTICLE_TEXTURE_SIZE,
                            height: PARTICLE_TEXTURE_SIZE,
                            depth_or_array_layers: 1,
                        },
                    );
                }
            }
            self.loaded_texture_count = pending.len();
            self.texture_array_view = self
                .texture_array
                .create_view(&wgpu::TextureViewDescriptor::default());
            self.texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Particle Texture Bind Group"),
                layout: &self.texture_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(&self.texture_array_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&self.texture_sampler),
                    },
                ],
            });
        }

        let delta_time = world.resources.window.timing.delta_time;
        self.time += delta_time;

        self.update_emitters(world);

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

        let params = SimParams {
            delta_time,
            time: self.time,
            max_particles: self.max_particles,
            _padding: 0,
        };
        queue.write_buffer(&self.params_buffer, 0, bytemuck::cast_slice(&[params]));

        if let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        {
            let view: Mat4 = camera_matrices.view;
            let projection: Mat4 = camera_matrices.projection;
            let view_projection = projection * view;

            let view_inverse = nalgebra_glm::inverse(&view);
            let camera_right = Vec3::new(
                view_inverse[(0, 0)],
                view_inverse[(1, 0)],
                view_inverse[(2, 0)],
            );
            let camera_up = Vec3::new(
                view_inverse[(0, 1)],
                view_inverse[(1, 1)],
                view_inverse[(2, 1)],
            );
            let camera_position = Vec3::new(
                view_inverse[(0, 3)],
                view_inverse[(1, 3)],
                view_inverse[(2, 3)],
            );

            let camera_uniforms = CameraUniforms {
                view: view.into(),
                projection: projection.into(),
                view_projection: view_projection.into(),
                camera_position: [camera_position.x, camera_position.y, camera_position.z, 1.0],
                camera_right: [camera_right.x, camera_right.y, camera_right.z, 0.0],
                camera_up: [camera_up.x, camera_up.y, camera_up.z, 0.0],
            };

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

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Particle Reset Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.reset_pipeline);
            compute_pass.set_bind_group(0, &self.compute_bind_group, &[]);
            compute_pass.dispatch_workgroups(1, 1, 1);
        }

        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Particle Update Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.update_pipeline);
            compute_pass.set_bind_group(0, &self.compute_bind_group, &[]);
            let workgroups = self.max_particles.div_ceil(WORKGROUP_SIZE);
            compute_pass.dispatch_workgroups(workgroups, 1, 1);
        }

        if !self.emitter_data.is_empty() {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Particle Spawn Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.spawn_pipeline);
            compute_pass.set_bind_group(0, &self.compute_bind_group, &[]);
            compute_pass.dispatch_workgroups(self.emitter_data.len() as u32, 1, 1);
        }

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

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

            if self.use_additive_blending {
                render_pass.set_pipeline(&self.render_pipeline_additive);
            } else {
                render_pass.set_pipeline(&self.render_pipeline);
            }
            render_pass.set_bind_group(0, &self.render_bind_group, &[]);
            render_pass.set_bind_group(1, &self.texture_bind_group, &[]);
            render_pass.draw_indirect(&self.draw_indirect_buffer, 0);
        }

        Ok(context.into_sub_graph_commands())
    }
}
