use crate::ecs::grass::components::GrassRegion;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec3};

const MAX_INSTANCES: u32 = 500_000;
const MAX_VISIBLE: u32 = 200_000;
const BLADE_VERTICES: u32 = 7;
const HEIGHTMAP_SIZE: u32 = 256;
const BEND_MAP_SIZE: u32 = 128;
const MAX_SPECIES: usize = 8;
const MAX_INTERACTORS: usize = 16;
const WORKGROUP_SIZE: u32 = 256;

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GrassInstance {
    position: [f32; 3],
    rotation: f32,
    height: f32,
    width: f32,
    species_index: u32,
    lod: u32,
    base_color: [f32; 4],
    tip_color: [f32; 4],
    bend: [f32; 2],
    _padding: [f32; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GrassSpeciesGpu {
    base_color: [f32; 4],
    tip_color: [f32; 4],
    sss_color: [f32; 4],
    sss_intensity: f32,
    specular_power: f32,
    specular_strength: f32,
    blade_curvature: f32,
    blade_width: f32,
    blade_height_min: f32,
    blade_height_max: f32,
    density_scale: f32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SpeciesWeights {
    weights0: [f32; 4],
    weights1: [f32; 4],
    count: u32,
    _pad: [u32; 3],
    _padding: [u32; 8],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct CullingUniforms {
    view_projection: [[f32; 4]; 4],
    camera_position: [f32; 4],
    lod_distances: [f32; 4],
    lod_density_scales: [f32; 4],
    total_instances: u32,
    max_visible: u32,
    time: f32,
    _padding: f32,
}

#[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],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GrassRenderUniforms {
    time: f32,
    wind_strength: f32,
    wind_frequency: f32,
    sss_intensity: f32,
    wind_direction: [f32; 2],
    interaction_strength: f32,
    _padding: f32,
    sun_direction: [f32; 3],
    sun_intensity: f32,
    sun_color: [f32; 3],
    ambient_intensity: f32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Interactor {
    position: [f32; 3],
    radius: f32,
    velocity: [f32; 3],
    strength: f32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct InteractionUniforms {
    interactor_count: u32,
    bend_map_size: u32,
    world_size: f32,
    world_center_x: f32,
    world_center_z: f32,
    decay_rate: f32,
    delta_time: f32,
    _padding: f32,
}

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

pub struct GrassPass {
    instance_buffer: wgpu::Buffer,
    _visible_indices_buffer: wgpu::Buffer,
    draw_indirect_buffer: wgpu::Buffer,
    species_buffer: wgpu::Buffer,
    species_weights_buffer: wgpu::Buffer,
    interactor_buffer: wgpu::Buffer,

    culling_uniforms_buffer: wgpu::Buffer,
    camera_uniforms_buffer: wgpu::Buffer,
    grass_render_uniforms_buffer: wgpu::Buffer,
    interaction_uniforms_buffer: wgpu::Buffer,

    heightmap_texture: wgpu::Texture,

    _bend_map_textures: [wgpu::Texture; 2],
    _bend_map_views: [wgpu::TextureView; 2],
    _bend_map_storage_views: [wgpu::TextureView; 2],
    _bend_map_sampler: wgpu::Sampler,
    bend_map_index: usize,

    culling_pipeline: wgpu::ComputePipeline,
    reset_pipeline: wgpu::ComputePipeline,
    _culling_bind_group_layout: wgpu::BindGroupLayout,
    culling_bind_group: wgpu::BindGroup,

    interaction_pipeline: wgpu::ComputePipeline,
    _interaction_bind_group_layout: wgpu::BindGroupLayout,
    interaction_bind_groups: [wgpu::BindGroup; 2],
    sample_bend_pipeline: wgpu::ComputePipeline,
    _sample_bend_bind_group_layout: wgpu::BindGroupLayout,
    sample_bend_bind_groups: [wgpu::BindGroup; 2],

    render_pipeline: wgpu::RenderPipeline,
    _render_bind_group_layout: wgpu::BindGroupLayout,
    render_bind_group: wgpu::BindGroup,

    time: f32,
    instance_count: u32,
    enabled: bool,
    initialized: bool,
    terrain_width: f32,
    terrain_depth: f32,
}

impl GrassPass {
    pub fn new(
        device: &wgpu::Device,
        color_format: wgpu::TextureFormat,
        depth_format: wgpu::TextureFormat,
    ) -> Self {
        let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Instance Buffer"),
            size: (std::mem::size_of::<GrassInstance>() as u64) * MAX_INSTANCES as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

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

        let draw_indirect_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Draw Indirect Buffer"),
            size: std::mem::size_of::<DrawIndirect>() as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let species_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Species Buffer"),
            size: (std::mem::size_of::<GrassSpeciesGpu>() as u64) * MAX_SPECIES as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let species_weights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Species Weights Buffer"),
            size: std::mem::size_of::<SpeciesWeights>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let interactor_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Interactor Buffer"),
            size: (std::mem::size_of::<Interactor>() as u64) * MAX_INTERACTORS as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let culling_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Culling Uniforms"),
            size: std::mem::size_of::<CullingUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

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

        let grass_render_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Render Uniforms"),
            size: std::mem::size_of::<GrassRenderUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let interaction_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Interaction Uniforms"),
            size: std::mem::size_of::<InteractionUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let heightmap_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Grass Heightmap"),
            size: wgpu::Extent3d {
                width: HEIGHTMAP_SIZE,
                height: HEIGHTMAP_SIZE,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        let bend_map_textures: [wgpu::Texture; 2] = std::array::from_fn(|index| {
            device.create_texture(&wgpu::TextureDescriptor {
                label: Some(&format!("Grass Bend Map {}", index)),
                size: wgpu::Extent3d {
                    width: BEND_MAP_SIZE,
                    height: BEND_MAP_SIZE,
                    depth_or_array_layers: 1,
                },
                mip_level_count: 1,
                sample_count: 1,
                dimension: wgpu::TextureDimension::D2,
                format: wgpu::TextureFormat::Rg32Float,
                usage: wgpu::TextureUsages::TEXTURE_BINDING
                    | wgpu::TextureUsages::STORAGE_BINDING
                    | wgpu::TextureUsages::COPY_DST,
                view_formats: &[],
            })
        });

        let bend_map_views: [wgpu::TextureView; 2] = std::array::from_fn(|index| {
            bend_map_textures[index].create_view(&wgpu::TextureViewDescriptor::default())
        });

        let bend_map_storage_views: [wgpu::TextureView; 2] = std::array::from_fn(|index| {
            bend_map_textures[index].create_view(&wgpu::TextureViewDescriptor {
                format: Some(wgpu::TextureFormat::Rg32Float),
                ..Default::default()
            })
        });

        let bend_map_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Grass Bend Map Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        let culling_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Grass Culling Shader"),
            source: wgpu::ShaderSource::Wgsl(
                include_str!("../../shaders/grass_culling.wgsl").into(),
            ),
        });

        let culling_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Culling Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        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: 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: false },
                            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 culling_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Grass Culling Pipeline Layout"),
                bind_group_layouts: &[&culling_bind_group_layout],
                push_constant_ranges: &[],
            });

        let culling_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Grass Culling Pipeline"),
            layout: Some(&culling_pipeline_layout),
            module: &culling_shader,
            entry_point: Some("main"),
            compilation_options: Default::default(),
            cache: None,
        });

        let reset_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Grass Reset Draw Command Pipeline"),
            layout: Some(&culling_pipeline_layout),
            module: &culling_shader,
            entry_point: Some("reset_draw_command"),
            compilation_options: Default::default(),
            cache: None,
        });

        let culling_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Grass Culling Bind Group"),
            layout: &culling_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: culling_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: instance_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: draw_indirect_buffer.as_entire_binding(),
                },
            ],
        });

        let interaction_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Grass Interaction Shader"),
            source: wgpu::ShaderSource::Wgsl(
                include_str!("../../shaders/grass_interaction.wgsl").into(),
            ),
        });

        let interaction_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Interaction Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        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: 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::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::StorageTexture {
                            access: wgpu::StorageTextureAccess::WriteOnly,
                            format: wgpu::TextureFormat::Rg32Float,
                            view_dimension: wgpu::TextureViewDimension::D2,
                        },
                        count: None,
                    },
                ],
            });

        let interaction_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Grass Interaction Pipeline Layout"),
                bind_group_layouts: &[&interaction_bind_group_layout],
                push_constant_ranges: &[],
            });

        let interaction_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Grass Interaction Pipeline"),
                layout: Some(&interaction_pipeline_layout),
                module: &interaction_shader,
                entry_point: Some("update_bend_map"),
                compilation_options: Default::default(),
                cache: None,
            });

        let interaction_bind_groups: [wgpu::BindGroup; 2] = std::array::from_fn(|index| {
            let read_index = index;
            let write_index = 1 - index;
            device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some(&format!("Grass Interaction Bind Group {}", index)),
                layout: &interaction_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: interaction_uniforms_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: interactor_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(&bend_map_views[read_index]),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::TextureView(
                            &bend_map_storage_views[write_index],
                        ),
                    },
                ],
            })
        });

        let sample_bend_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Sample Bend 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::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                ],
            });

        let sample_bend_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Grass Sample Bend Pipeline Layout"),
                bind_group_layouts: &[
                    &interaction_bind_group_layout,
                    &sample_bend_bind_group_layout,
                ],
                push_constant_ranges: &[],
            });

        let sample_bend_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Grass Sample Bend Pipeline"),
                layout: Some(&sample_bend_pipeline_layout),
                module: &interaction_shader,
                entry_point: Some("sample_bend_for_instances"),
                compilation_options: Default::default(),
                cache: None,
            });

        let sample_bend_bind_groups: [wgpu::BindGroup; 2] = std::array::from_fn(|_| {
            device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Grass Sample Bend Bind Group"),
                layout: &sample_bend_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: instance_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&bend_map_sampler),
                    },
                ],
            })
        });

        let render_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Grass Render Shader"),
            source: wgpu::ShaderSource::Wgsl(
                include_str!("../../shaders/grass_render.wgsl").into(),
            ),
        });

        let render_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Render 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: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        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: 4,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

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

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Grass 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::TriangleStrip,
                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: depth_format,
                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 render_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Grass Render Bind Group"),
            layout: &render_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: camera_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: grass_render_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: instance_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: species_buffer.as_entire_binding(),
                },
            ],
        });

        Self {
            instance_buffer,
            _visible_indices_buffer: visible_indices_buffer,
            draw_indirect_buffer,
            species_buffer,
            species_weights_buffer,
            interactor_buffer,
            culling_uniforms_buffer,
            camera_uniforms_buffer,
            grass_render_uniforms_buffer,
            interaction_uniforms_buffer,
            heightmap_texture,
            _bend_map_textures: bend_map_textures,
            _bend_map_views: bend_map_views,
            _bend_map_storage_views: bend_map_storage_views,
            _bend_map_sampler: bend_map_sampler,
            bend_map_index: 0,
            culling_pipeline,
            reset_pipeline,
            _culling_bind_group_layout: culling_bind_group_layout,
            culling_bind_group,
            interaction_pipeline,
            _interaction_bind_group_layout: interaction_bind_group_layout,
            interaction_bind_groups,
            sample_bend_pipeline,
            _sample_bend_bind_group_layout: sample_bend_bind_group_layout,
            sample_bend_bind_groups,
            render_pipeline,
            _render_bind_group_layout: render_bind_group_layout,
            render_bind_group,
            time: 0.0,
            instance_count: 0,
            enabled: false,
            initialized: false,
            terrain_width: 100.0,
            terrain_depth: 100.0,
        }
    }

    fn generate_heightmap(&mut self, queue: &wgpu::Queue, region: &GrassRegion) {
        if let Some(terrain_config) = &region.terrain_config {
            self.terrain_width = terrain_config.width;
            self.terrain_depth = terrain_config.depth;

            let mut heightmap_data = vec![0.0f32; (HEIGHTMAP_SIZE * HEIGHTMAP_SIZE) as usize];

            for z in 0..HEIGHTMAP_SIZE {
                for x in 0..HEIGHTMAP_SIZE {
                    let u = x as f32 / (HEIGHTMAP_SIZE - 1) as f32;
                    let v = z as f32 / (HEIGHTMAP_SIZE - 1) as f32;

                    let world_x = (u - 0.5) * terrain_config.width;
                    let world_z = (v - 0.5) * terrain_config.depth;

                    let height = crate::ecs::terrain::sample_terrain_height(
                        world_x,
                        world_z,
                        terrain_config,
                    );

                    heightmap_data[(z * HEIGHTMAP_SIZE + x) as usize] = height;
                }
            }

            queue.write_texture(
                wgpu::TexelCopyTextureInfo {
                    texture: &self.heightmap_texture,
                    mip_level: 0,
                    origin: wgpu::Origin3d::ZERO,
                    aspect: wgpu::TextureAspect::All,
                },
                bytemuck::cast_slice(&heightmap_data),
                wgpu::TexelCopyBufferLayout {
                    offset: 0,
                    bytes_per_row: Some(HEIGHTMAP_SIZE * 4),
                    rows_per_image: Some(HEIGHTMAP_SIZE),
                },
                wgpu::Extent3d {
                    width: HEIGHTMAP_SIZE,
                    height: HEIGHTMAP_SIZE,
                    depth_or_array_layers: 1,
                },
            );
        }
    }

    fn upload_species_data(&self, queue: &wgpu::Queue, region: &GrassRegion) {
        let mut species_data = vec![
            GrassSpeciesGpu {
                base_color: [0.0; 4],
                tip_color: [0.0; 4],
                sss_color: [0.0; 4],
                sss_intensity: 0.0,
                specular_power: 0.0,
                specular_strength: 0.0,
                blade_curvature: 0.0,
                blade_width: 0.0,
                blade_height_min: 0.0,
                blade_height_max: 0.0,
                density_scale: 0.0,
            };
            MAX_SPECIES
        ];

        for (index, species) in region.species.iter().enumerate().take(MAX_SPECIES) {
            species_data[index] = GrassSpeciesGpu {
                base_color: species.base_color,
                tip_color: species.tip_color,
                sss_color: species.sss_color,
                sss_intensity: species.sss_intensity,
                specular_power: species.specular_power,
                specular_strength: species.specular_strength,
                blade_curvature: species.blade_curvature,
                blade_width: species.blade_width,
                blade_height_min: species.blade_height_min,
                blade_height_max: species.blade_height_max,
                density_scale: species.density_scale,
            };
        }

        queue.write_buffer(&self.species_buffer, 0, bytemuck::cast_slice(&species_data));

        let normalized = region.normalized_weights();
        let mut weights = SpeciesWeights {
            weights0: [0.0; 4],
            weights1: [0.0; 4],
            count: normalized.len().min(MAX_SPECIES) as u32,
            _pad: [0; 3],
            _padding: [0; 8],
        };

        for (index, &weight) in normalized.iter().enumerate().take(8) {
            if index < 4 {
                weights.weights0[index] = weight;
            } else {
                weights.weights1[index - 4] = weight;
            }
        }

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

    fn initialize_instances_cpu(&mut self, queue: &wgpu::Queue, region: &GrassRegion) {
        if region.species.is_empty() {
            return;
        }

        let terrain_width = self.terrain_width;
        let terrain_depth = self.terrain_depth;
        let area = terrain_width * terrain_depth;
        let blades_per_sq_meter = region.config.blades_per_patch as f32 / 8.0;
        let blade_count = ((area * blades_per_sq_meter) as usize).min(MAX_INSTANCES as usize);

        let mut instances = Vec::with_capacity(blade_count);

        let normalized_weights = region.normalized_weights();
        let mut cumulative_weights = Vec::with_capacity(normalized_weights.len());
        let mut sum = 0.0;
        for weight in &normalized_weights {
            sum += weight;
            cumulative_weights.push(sum);
        }

        for index in 0..blade_count {
            let mut rng_state = (index as u32).wrapping_mul(2654435761);

            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let u = rng_state as f32 / u32::MAX as f32;
            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let v = rng_state as f32 / u32::MAX as f32;

            let world_x = (u - 0.5) * terrain_width;
            let world_z = (v - 0.5) * terrain_depth;

            let world_y = if let Some(terrain_config) = &region.terrain_config {
                crate::ecs::terrain::sample_terrain_height(world_x, world_z, terrain_config)
            } else {
                0.0
            };

            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let species_roll = rng_state as f32 / u32::MAX as f32;
            let species_index = cumulative_weights
                .iter()
                .position(|&w| species_roll <= w)
                .unwrap_or(0);
            let species = &region.species[species_index];

            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let height_t = rng_state as f32 / u32::MAX as f32;
            let height = species.blade_height_min
                + height_t * (species.blade_height_max - species.blade_height_min);

            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let width = species.blade_width * (0.8 + (rng_state as f32 / u32::MAX as f32) * 0.4);

            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let rotation = (rng_state as f32 / u32::MAX as f32) * std::f32::consts::TAU;

            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
            let color_var = 0.85 + (rng_state as f32 / u32::MAX as f32) * 0.3;

            instances.push(GrassInstance {
                position: [world_x, world_y, world_z],
                rotation,
                height,
                width,
                species_index: species_index as u32,
                lod: 0,
                base_color: [
                    species.base_color[0] * color_var,
                    species.base_color[1] * color_var,
                    species.base_color[2] * color_var,
                    species.base_color[3],
                ],
                tip_color: [
                    species.tip_color[0] * color_var,
                    species.tip_color[1] * color_var,
                    species.tip_color[2] * color_var,
                    species.tip_color[3],
                ],
                bend: [0.0, 0.0],
                _padding: [0.0, 0.0],
            });
        }

        queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
        self.instance_count = instances.len() as u32;
        self.initialized = true;
    }
}

impl PassNode<World> for GrassPass {
    fn name(&self) -> &str {
        "grass_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) {
        self.time += world.resources.window.timing.delta_time;

        let grass_entities: Vec<_> = world.query_entities(crate::ecs::GRASS_REGION).collect();

        if grass_entities.is_empty() {
            self.enabled = false;
            return;
        }

        for entity in grass_entities {
            if let Some(region) = world.get_grass_region(entity) {
                if !region.enabled {
                    self.enabled = false;
                    continue;
                }

                self.enabled = true;

                if !self.initialized {
                    self.generate_heightmap(queue, region);
                    self.upload_species_data(queue, region);
                    self.initialize_instances_cpu(queue, region);
                }

                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 camera_position = camera_matrices.camera_position;

                    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,
                        ],
                    };
                    queue.write_buffer(
                        &self.camera_uniforms_buffer,
                        0,
                        bytemuck::cast_slice(&[camera_uniforms]),
                    );

                    let culling_uniforms = CullingUniforms {
                        view_projection: view_projection.into(),
                        camera_position: [
                            camera_position.x,
                            camera_position.y,
                            camera_position.z,
                            1.0,
                        ],
                        lod_distances: region.config.lod_distances,
                        lod_density_scales: region.config.lod_density_scales,
                        total_instances: self.instance_count,
                        max_visible: MAX_VISIBLE,
                        time: self.time,
                        _padding: 0.0,
                    };
                    queue.write_buffer(
                        &self.culling_uniforms_buffer,
                        0,
                        bytemuck::cast_slice(&[culling_uniforms]),
                    );

                    let sun_dir = nalgebra_glm::normalize(&Vec3::new(0.3, 0.8, 0.5));
                    let grass_render_uniforms = GrassRenderUniforms {
                        time: self.time,
                        wind_strength: region.config.wind_strength,
                        wind_frequency: region.config.wind_frequency,
                        sss_intensity: 0.5,
                        wind_direction: region.config.wind_direction,
                        interaction_strength: region.config.interaction_strength,
                        _padding: 0.0,
                        sun_direction: [sun_dir.x, sun_dir.y, sun_dir.z],
                        sun_intensity: 2.0,
                        sun_color: [1.0, 0.95, 0.9],
                        ambient_intensity: 0.3,
                    };
                    queue.write_buffer(
                        &self.grass_render_uniforms_buffer,
                        0,
                        bytemuck::cast_slice(&[grass_render_uniforms]),
                    );

                    let mut interactors = Vec::new();
                    if region.config.interactors_enabled {
                        for entity in world.query_entities(crate::ecs::GRASS_INTERACTOR) {
                            if let (Some(interactor), Some(transform)) = (
                                world.get_grass_interactor(entity),
                                world.get_global_transform(entity),
                            ) {
                                let pos = transform.translation();
                                interactors.push(Interactor {
                                    position: [pos.x, pos.y, pos.z],
                                    radius: interactor.radius,
                                    velocity: [0.0, 0.0, 0.0],
                                    strength: interactor.strength,
                                });
                            }
                        }
                    }
                    let interactor_count = interactors.len().min(MAX_INTERACTORS);
                    if !interactors.is_empty() {
                        queue.write_buffer(
                            &self.interactor_buffer,
                            0,
                            bytemuck::cast_slice(&interactors[..interactor_count]),
                        );
                    }

                    let interaction_uniforms = InteractionUniforms {
                        interactor_count: interactor_count as u32,
                        bend_map_size: BEND_MAP_SIZE,
                        world_size: self.terrain_width.max(self.terrain_depth),
                        world_center_x: 0.0,
                        world_center_z: 0.0,
                        decay_rate: 5.0,
                        delta_time: world.resources.window.timing.delta_time,
                        _padding: 0.0,
                    };
                    queue.write_buffer(
                        &self.interaction_uniforms_buffer,
                        0,
                        bytemuck::cast_slice(&[interaction_uniforms]),
                    );

                    let initial_draw_command = DrawIndirect {
                        vertex_count: BLADE_VERTICES,
                        instance_count: 0,
                        first_vertex: 0,
                        first_instance: 0,
                    };
                    queue.write_buffer(
                        &self.draw_indirect_buffer,
                        0,
                        bytemuck::cast_slice(&[initial_draw_command]),
                    );
                }
            }
        }
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        if !self.enabled || self.instance_count == 0 {
            return Ok(context.into_sub_graph_commands());
        }

        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Grass Interaction Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.interaction_pipeline);
            compute_pass.set_bind_group(0, &self.interaction_bind_groups[self.bend_map_index], &[]);
            let workgroups = BEND_MAP_SIZE.div_ceil(16);
            compute_pass.dispatch_workgroups(workgroups, workgroups, 1);
        }

        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Grass Sample Bend Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.sample_bend_pipeline);
            compute_pass.set_bind_group(
                0,
                &self.interaction_bind_groups[1 - self.bend_map_index],
                &[],
            );
            compute_pass.set_bind_group(1, &self.sample_bend_bind_groups[self.bend_map_index], &[]);
            let workgroups = self.instance_count.div_ceil(WORKGROUP_SIZE);
            compute_pass.dispatch_workgroups(workgroups, 1, 1);
        }

        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Grass Reset Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.reset_pipeline);
            compute_pass.set_bind_group(0, &self.culling_bind_group, &[]);
            compute_pass.dispatch_workgroups(1, 1, 1);
        }

        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Grass Culling Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.culling_pipeline);
            compute_pass.set_bind_group(0, &self.culling_bind_group, &[]);
            let workgroups = self.instance_count.div_ceil(WORKGROUP_SIZE);
            compute_pass.dispatch_workgroups(workgroups, 1, 1);
        }

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

        {
            let mut render_pass = context
                .encoder
                .begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("Grass 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,
                });

            render_pass.set_pipeline(&self.render_pipeline);
            render_pass.set_bind_group(0, &self.render_bind_group, &[]);
            render_pass.draw_indirect(&self.draw_indirect_buffer, 0);
        }

        self.bend_map_index = 1 - self.bend_map_index;

        Ok(context.into_sub_graph_commands())
    }
}
