use crate::ecs::grass::{GrassDomain, GrassSettings};
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec3};
use wgpu::util::DeviceExt;

const MAX_BLADES: u32 = 524_288;
const MAX_BLADE_TILES: u32 = 4096;
const MAX_FAR_TILES: u32 = 8192;
pub(crate) const MAX_TYPES: usize = 8;
const MAX_PUSHERS: usize = 8;
const TILE_SIZE: f32 = 8.0;
const HIGH_INDEX_COUNT: u32 = 33;
const LOW_INDEX_COUNT: u32 = 15;
const LOW_FIRST_INDEX: u32 = 33;
const LOW_DRAW_OFFSET: u64 = 20;
const FAR_DRAW_OFFSET: u64 = 40;
const FAR_VERTEX_COUNT: u32 = 384;

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GrassUniforms {
    view_projection: [[f32; 4]; 4],
    frustum_planes: [[f32; 4]; 6],
    camera_position: [f32; 4],
    camera_right: [f32; 4],
    camera_tile: [i32; 4],
    sun_direction: [f32; 4],
    sun_color: [f32; 4],
    ambient: [f32; 4],
    wind: [f32; 4],
    radii: [f32; 4],
    params: [f32; 4],
    counts: [u32; 4],
    domain: [f32; 4],
    height_map: [f32; 4],
    height_bounds: [f32; 4],
    splat: [f32; 4],
    pushers: [[f32; 4]; 8],
    pusher_strength: [[f32; 4]; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct GpuGrassType {
    shape: [f32; 4],
    curve: [f32; 4],
    color_base: [f32; 4],
    color_tip: [f32; 4],
    misc: [f32; 4],
    misc2: [f32; 4],
}

pub struct GrassPass {
    uniforms_buffer: wgpu::Buffer,
    types_buffer: wgpu::Buffer,
    dispatch_buffer: wgpu::Buffer,
    draw_commands_buffer: wgpu::Buffer,
    blade_tile_buffer: wgpu::Buffer,
    far_tile_buffer: wgpu::Buffer,
    high_blade_buffer: wgpu::Buffer,
    low_blade_buffer: wgpu::Buffer,
    index_buffer: wgpu::Buffer,
    cull_pipeline: wgpu::ComputePipeline,
    generate_pipeline: wgpu::ComputePipeline,
    high_pipeline: wgpu::RenderPipeline,
    low_pipeline: wgpu::RenderPipeline,
    far_pipeline: wgpu::RenderPipeline,
    cull_bind_group: wgpu::BindGroup,
    generate_bind_group_layout: wgpu::BindGroupLayout,
    render_bind_group_layout: wgpu::BindGroupLayout,
    generate_bind_group: wgpu::BindGroup,
    high_bind_group: wgpu::BindGroup,
    low_bind_group: wgpu::BindGroup,
    height_texture: wgpu::Texture,
    height_texture_view: wgpu::TextureView,
    height_resolution: u32,
    height_revision: Option<u64>,
    #[cfg(feature = "terrain")]
    terrain_share: Option<crate::ecs::terrain::TerrainShare>,
    #[cfg(feature = "terrain")]
    terrain_bound: bool,
    types_revision: Option<u64>,
    tile_dispatch_workgroups: u32,
    time: f32,
    active: bool,
}

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

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

        let dispatch_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Dispatch Buffer"),
            size: 16,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let draw_commands_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Draw Commands Buffer"),
            size: 64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let blade_tile_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Blade Tile Buffer"),
            size: (MAX_BLADE_TILES as usize * 16) as u64,
            usage: wgpu::BufferUsages::STORAGE,
            mapped_at_creation: false,
        });

        let far_tile_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Far Tile Buffer"),
            size: (MAX_FAR_TILES as usize * 16) as u64,
            usage: wgpu::BufferUsages::STORAGE,
            mapped_at_creation: false,
        });

        let high_blade_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass High Blade Buffer"),
            size: (MAX_BLADES as usize * 64) as u64,
            usage: wgpu::BufferUsages::STORAGE,
            mapped_at_creation: false,
        });

        let low_blade_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Grass Low Blade Buffer"),
            size: (MAX_BLADES as usize * 64) as u64,
            usage: wgpu::BufferUsages::STORAGE,
            mapped_at_creation: false,
        });

        let indices = build_indices();
        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Grass Index Buffer"),
            contents: bytemuck::cast_slice(&indices),
            usage: wgpu::BufferUsages::INDEX,
        });

        let (height_texture, height_texture_view) = create_height_texture(device, 1);

        let compute_stage = wgpu::ShaderStages::COMPUTE;
        let cull_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Cull Bind Group Layout"),
                entries: &[
                    buffer_layout_entry(0, compute_stage, wgpu::BufferBindingType::Uniform),
                    buffer_layout_entry(
                        2,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                    buffer_layout_entry(
                        3,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                    buffer_layout_entry(
                        4,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                    buffer_layout_entry(
                        5,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                ],
            });

        let cull_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Grass Cull Bind Group"),
            layout: &cull_bind_group_layout,
            entries: &[
                bind_entry(0, &uniforms_buffer),
                bind_entry(2, &dispatch_buffer),
                bind_entry(3, &blade_tile_buffer),
                bind_entry(4, &far_tile_buffer),
                bind_entry(5, &draw_commands_buffer),
            ],
        });

        let generate_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Generate Bind Group Layout"),
                entries: &[
                    buffer_layout_entry(0, compute_stage, wgpu::BufferBindingType::Uniform),
                    buffer_layout_entry(
                        1,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: true },
                    ),
                    buffer_layout_entry(
                        3,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: true },
                    ),
                    buffer_layout_entry(
                        5,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                    buffer_layout_entry(
                        6,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                    buffer_layout_entry(
                        7,
                        compute_stage,
                        wgpu::BufferBindingType::Storage { read_only: false },
                    ),
                    texture_layout_entry(8, compute_stage),
                ],
            });

        let binding_resources = GrassBindingResources {
            uniforms: &uniforms_buffer,
            types: &types_buffer,
            blade_tiles: &blade_tile_buffer,
            draw_commands: &draw_commands_buffer,
            high_blades: &high_blade_buffer,
            low_blades: &low_blade_buffer,
            far_tiles: &far_tile_buffer,
            height_view: &height_texture_view,
        };
        let generate_bind_group =
            create_generate_bind_group(device, &generate_bind_group_layout, &binding_resources);

        let render_stage = wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT;
        let render_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Grass Render Bind Group Layout"),
                entries: &[
                    buffer_layout_entry(0, render_stage, wgpu::BufferBindingType::Uniform),
                    buffer_layout_entry(
                        1,
                        render_stage,
                        wgpu::BufferBindingType::Storage { read_only: true },
                    ),
                    buffer_layout_entry(
                        2,
                        render_stage,
                        wgpu::BufferBindingType::Storage { read_only: true },
                    ),
                    buffer_layout_entry(
                        3,
                        render_stage,
                        wgpu::BufferBindingType::Storage { read_only: true },
                    ),
                    texture_layout_entry(4, render_stage),
                ],
            });

        let high_bind_group = create_render_bind_group(
            device,
            &render_bind_group_layout,
            &binding_resources,
            &high_blade_buffer,
            "Grass High Render Bind Group",
        );

        let low_bind_group = create_render_bind_group(
            device,
            &render_bind_group_layout,
            &binding_resources,
            &low_blade_buffer,
            "Grass Low Render Bind Group",
        );

        let cull_shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "Grass Cull Shader",
            concat!(
                include_str!("../../shaders/terrain_common.wgsl"),
                include_str!("../../shaders/grass_common.wgsl"),
                include_str!("../../shaders/grass_cull.wgsl"),
            ),
        );

        let generate_shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "Grass Generate Shader",
            concat!(
                include_str!("../../shaders/terrain_common.wgsl"),
                include_str!("../../shaders/grass_common.wgsl"),
                include_str!("../../shaders/grass_generate.wgsl"),
            ),
        );

        let render_shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "Grass Render Shader",
            concat!(
                include_str!("../../shaders/terrain_common.wgsl"),
                include_str!("../../shaders/grass_common.wgsl"),
                include_str!("../../shaders/grass_render.wgsl"),
            ),
        );

        let far_shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "Grass Far Shader",
            concat!(
                include_str!("../../shaders/terrain_common.wgsl"),
                include_str!("../../shaders/grass_common.wgsl"),
                include_str!("../../shaders/grass_far.wgsl"),
            ),
        );

        let cull_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Grass Cull Pipeline Layout"),
            bind_group_layouts: &[Some(&cull_bind_group_layout)],
            immediate_size: 0,
        });

        let generate_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Grass Generate Pipeline Layout"),
                bind_group_layouts: &[Some(&generate_bind_group_layout)],
                immediate_size: 0,
            });

        let cull_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Grass Cull Pipeline"),
            layout: Some(&cull_pipeline_layout),
            module: &cull_shader,
            entry_point: Some("cull_tiles"),
            compilation_options: Default::default(),
            cache: None,
        });

        let generate_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Grass Generate Pipeline"),
            layout: Some(&generate_pipeline_layout),
            module: &generate_shader,
            entry_point: Some("generate_blades"),
            compilation_options: Default::default(),
            cache: None,
        });

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

        let high_pipeline = create_render_pipeline(
            device,
            &render_pipeline_layout,
            &render_shader,
            "vs_high",
            "fs_blade",
            color_format,
            "Grass High Pipeline",
        );
        let low_pipeline = create_render_pipeline(
            device,
            &render_pipeline_layout,
            &render_shader,
            "vs_low",
            "fs_blade",
            color_format,
            "Grass Low Pipeline",
        );
        let far_pipeline = create_render_pipeline(
            device,
            &render_pipeline_layout,
            &far_shader,
            "vs_far",
            "fs_far",
            color_format,
            "Grass Far Pipeline",
        );

        Self {
            uniforms_buffer,
            types_buffer,
            dispatch_buffer,
            draw_commands_buffer,
            blade_tile_buffer,
            far_tile_buffer,
            high_blade_buffer,
            low_blade_buffer,
            index_buffer,
            cull_pipeline,
            generate_pipeline,
            high_pipeline,
            low_pipeline,
            far_pipeline,
            cull_bind_group,
            generate_bind_group_layout,
            render_bind_group_layout,
            generate_bind_group,
            high_bind_group,
            low_bind_group,
            height_texture,
            height_texture_view,
            height_resolution: 1,
            height_revision: None,
            #[cfg(feature = "terrain")]
            terrain_share: None,
            #[cfg(feature = "terrain")]
            terrain_bound: false,
            types_revision: None,
            tile_dispatch_workgroups: 0,
            time: 0.0,
            active: false,
        }
    }

    fn rebuild_height_bindings(
        &mut self,
        device: &wgpu::Device,
        terrain_view: Option<&wgpu::TextureView>,
    ) {
        let height_view = terrain_view.unwrap_or(&self.height_texture_view);
        let binding_resources = GrassBindingResources {
            uniforms: &self.uniforms_buffer,
            types: &self.types_buffer,
            blade_tiles: &self.blade_tile_buffer,
            draw_commands: &self.draw_commands_buffer,
            high_blades: &self.high_blade_buffer,
            low_blades: &self.low_blade_buffer,
            far_tiles: &self.far_tile_buffer,
            height_view,
        };
        let generate_bind_group = create_generate_bind_group(
            device,
            &self.generate_bind_group_layout,
            &binding_resources,
        );
        let high_bind_group = create_render_bind_group(
            device,
            &self.render_bind_group_layout,
            &binding_resources,
            &self.high_blade_buffer,
            "Grass High Render Bind Group",
        );
        let low_bind_group = create_render_bind_group(
            device,
            &self.render_bind_group_layout,
            &binding_resources,
            &self.low_blade_buffer,
            "Grass Low Render Bind Group",
        );
        self.generate_bind_group = generate_bind_group;
        self.high_bind_group = high_bind_group;
        self.low_bind_group = low_bind_group;
    }

    fn sync_height_map(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
        let Some(height_map) = &world.resources.grass.height_map else {
            return;
        };
        let resolution = height_map.resolution.max(1);
        if height_map.heights.len() != (resolution * resolution) as usize {
            return;
        }
        if resolution != self.height_resolution {
            let (texture, view) = create_height_texture(device, resolution);
            self.height_texture = texture;
            self.height_texture_view = view;
            self.height_resolution = resolution;
            self.height_revision = None;
            #[cfg(feature = "terrain")]
            let rebuild = !self.terrain_bound;
            #[cfg(not(feature = "terrain"))]
            let rebuild = true;
            if rebuild {
                self.rebuild_height_bindings(device, None);
            }
        }
        if self.height_revision != Some(height_map.revision) {
            queue.write_texture(
                wgpu::TexelCopyTextureInfo {
                    texture: &self.height_texture,
                    mip_level: 0,
                    origin: wgpu::Origin3d::ZERO,
                    aspect: wgpu::TextureAspect::All,
                },
                bytemuck::cast_slice(&height_map.heights),
                wgpu::TexelCopyBufferLayout {
                    offset: 0,
                    bytes_per_row: Some(resolution * 4),
                    rows_per_image: Some(resolution),
                },
                wgpu::Extent3d {
                    width: resolution,
                    height: resolution,
                    depth_or_array_layers: 1,
                },
            );
            self.height_revision = Some(height_map.revision);
        }
    }
}

fn create_height_texture(
    device: &wgpu::Device,
    resolution: u32,
) -> (wgpu::Texture, wgpu::TextureView) {
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("Grass Height Texture"),
        size: wgpu::Extent3d {
            width: resolution,
            height: resolution,
            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 view = texture.create_view(&wgpu::TextureViewDescriptor {
        dimension: Some(wgpu::TextureViewDimension::D2Array),
        ..Default::default()
    });
    (texture, view)
}

fn texture_layout_entry(
    binding: u32,
    visibility: wgpu::ShaderStages,
) -> wgpu::BindGroupLayoutEntry {
    wgpu::BindGroupLayoutEntry {
        binding,
        visibility,
        ty: wgpu::BindingType::Texture {
            multisampled: false,
            view_dimension: wgpu::TextureViewDimension::D2Array,
            sample_type: wgpu::TextureSampleType::Float { filterable: false },
        },
        count: None,
    }
}

struct GrassBindingResources<'resources> {
    uniforms: &'resources wgpu::Buffer,
    types: &'resources wgpu::Buffer,
    blade_tiles: &'resources wgpu::Buffer,
    draw_commands: &'resources wgpu::Buffer,
    high_blades: &'resources wgpu::Buffer,
    low_blades: &'resources wgpu::Buffer,
    far_tiles: &'resources wgpu::Buffer,
    height_view: &'resources wgpu::TextureView,
}

fn create_generate_bind_group(
    device: &wgpu::Device,
    layout: &wgpu::BindGroupLayout,
    resources: &GrassBindingResources<'_>,
) -> wgpu::BindGroup {
    device.create_bind_group(&wgpu::BindGroupDescriptor {
        label: Some("Grass Generate Bind Group"),
        layout,
        entries: &[
            bind_entry(0, resources.uniforms),
            bind_entry(1, resources.types),
            bind_entry(3, resources.blade_tiles),
            bind_entry(5, resources.draw_commands),
            bind_entry(6, resources.high_blades),
            bind_entry(7, resources.low_blades),
            wgpu::BindGroupEntry {
                binding: 8,
                resource: wgpu::BindingResource::TextureView(resources.height_view),
            },
        ],
    })
}

fn create_render_bind_group(
    device: &wgpu::Device,
    layout: &wgpu::BindGroupLayout,
    resources: &GrassBindingResources<'_>,
    blade_buffer: &wgpu::Buffer,
    label: &str,
) -> wgpu::BindGroup {
    device.create_bind_group(&wgpu::BindGroupDescriptor {
        label: Some(label),
        layout,
        entries: &[
            bind_entry(0, resources.uniforms),
            bind_entry(1, resources.types),
            bind_entry(2, blade_buffer),
            bind_entry(3, resources.far_tiles),
            wgpu::BindGroupEntry {
                binding: 4,
                resource: wgpu::BindingResource::TextureView(resources.height_view),
            },
        ],
    })
}

fn buffer_layout_entry(
    binding: u32,
    visibility: wgpu::ShaderStages,
    ty: wgpu::BufferBindingType,
) -> wgpu::BindGroupLayoutEntry {
    wgpu::BindGroupLayoutEntry {
        binding,
        visibility,
        ty: wgpu::BindingType::Buffer {
            ty,
            has_dynamic_offset: false,
            min_binding_size: None,
        },
        count: None,
    }
}

fn bind_entry(binding: u32, buffer: &wgpu::Buffer) -> wgpu::BindGroupEntry<'_> {
    wgpu::BindGroupEntry {
        binding,
        resource: buffer.as_entire_binding(),
    }
}

fn create_render_pipeline(
    device: &wgpu::Device,
    layout: &wgpu::PipelineLayout,
    shader: &wgpu::ShaderModule,
    vertex_entry: &str,
    fragment_entry: &str,
    color_format: wgpu::TextureFormat,
    label: &str,
) -> wgpu::RenderPipeline {
    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label: Some(label),
        layout: Some(layout),
        vertex: wgpu::VertexState {
            module: shader,
            entry_point: Some(vertex_entry),
            buffers: &[],
            compilation_options: Default::default(),
        },
        fragment: Some(wgpu::FragmentState {
            module: shader,
            entry_point: Some(fragment_entry),
            targets: &[Some(wgpu::ColorTargetState {
                format: color_format,
                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::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(true),
            depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
            stencil: wgpu::StencilState::default(),
            bias: wgpu::DepthBiasState::default(),
        }),
        multisample: wgpu::MultisampleState::default(),
        multiview_mask: None,
        cache: None,
    })
}

fn build_indices() -> [u32; 48] {
    [
        0, 1, 2, 2, 1, 3, 2, 3, 4, 4, 3, 5, 4, 5, 6, 6, 5, 7, 8, 9, 10, 10, 9, 11, 10, 11, 12, 12,
        11, 13, 12, 13, 14, 0, 1, 2, 2, 1, 3, 2, 3, 4, 4, 3, 5, 4, 5, 6,
    ]
}

pub(crate) fn gpu_types(settings: &GrassSettings) -> [GpuGrassType; MAX_TYPES] {
    let mut output = [GpuGrassType::default(); MAX_TYPES];
    for (slot, params) in output.iter_mut().zip(settings.types.iter()) {
        *slot = GpuGrassType {
            shape: [
                params.height_min,
                params.height_max,
                params.width,
                params.density,
            ],
            curve: [
                params.tilt_min,
                params.tilt_max,
                params.bend_min,
                params.bend_max,
            ],
            color_base: params.color_base,
            color_tip: params.color_tip,
            misc: [
                params.gloss,
                params.wind_response,
                params.clump_height_variance,
                params.clump_pull,
            ],
            misc2: [params.clump_face_away, params.vertex_distribution, 0.0, 0.0],
        };
    }
    output
}

fn query_sun(world: &World) -> (Vec3, Vec3) {
    for entity in world
        .core
        .query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
    {
        if let (Some(light), Some(transform)) = (
            world.core.get_light(entity),
            world.core.get_global_transform(entity),
        ) && light.light_type == crate::ecs::light::components::LightType::Directional
        {
            let toward_light = -transform.forward_vector();
            let color = light.color * light.intensity;
            return (toward_light.normalize(), color);
        }
    }
    (
        Vec3::new(0.35, 0.8, 0.35).normalize(),
        Vec3::new(4.0, 3.8, 3.2),
    )
}

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) {
        let settings = &world.resources.grass;
        self.active = settings.enabled && !settings.types.is_empty();
        if !self.active {
            return;
        }
        self.sync_height_map(device, queue, world);
        #[cfg(feature = "terrain")]
        let terrain_sample = {
            if self.terrain_share.is_none() {
                self.terrain_share = Some(world.resources.terrain_render.share.clone());
            }
            let terrain_enabled = world.resources.terrain.enabled;
            let terrain_mode = self
                .terrain_share
                .as_ref()
                .expect("terrain share")
                .clone()
                .with(|shared| {
                    if terrain_enabled && shared.cache_ready {
                        shared
                            .cache_view
                            .clone()
                            .map(|view| (view, shared.grass_texel_size))
                    } else {
                        None
                    }
                });
            match &terrain_mode {
                Some((view, _)) => {
                    if !self.terrain_bound {
                        let view = view.clone();
                        self.rebuild_height_bindings(device, Some(&view));
                        self.terrain_bound = true;
                    }
                }
                None => {
                    if self.terrain_bound {
                        self.rebuild_height_bindings(device, None);
                        self.terrain_bound = false;
                    }
                }
            }
            terrain_mode.map(|(_, texel_size)| {
                (
                    [0.0, 0.0, texel_size, 2.0],
                    [
                        world.resources.terrain.height_min,
                        world.resources.terrain.height_max,
                        crate::ecs::terrain::TERRAIN_GRASS_LEVEL as f32,
                        (world
                            .resources
                            .terrain
                            .level_count
                            .clamp(2, crate::ecs::terrain::TERRAIN_COLLIDER_LAYER)
                            - 1) as f32,
                    ],
                )
            })
        };
        #[cfg(not(feature = "terrain"))]
        let terrain_sample: Option<([f32; 4], [f32; 4])> = None;
        #[cfg(feature = "terrain")]
        let terrain_splat = [
            world.resources.terrain.rock_slope,
            world.resources.terrain.snow_height,
            0.0,
            0.0,
        ];
        #[cfg(not(feature = "terrain"))]
        let terrain_splat = [0.0f32; 4];
        let settings = &world.resources.grass;
        let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        else {
            self.active = false;
            return;
        };

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

        let view: Mat4 = camera_matrices.view;
        let projection: Mat4 = camera_matrices.projection;
        let view_projection = projection * view;
        let frustum_planes = super::extract_frustum_planes(&view_projection);
        let view_inverse = nalgebra_glm::inverse(&view);
        let camera_position = Vec3::new(
            view_inverse[(0, 3)],
            view_inverse[(1, 3)],
            view_inverse[(2, 3)],
        );
        let camera_right = Vec3::new(
            view_inverse[(0, 0)],
            view_inverse[(1, 0)],
            view_inverse[(2, 0)],
        );

        let camera_tile_x = (camera_position.x / TILE_SIZE).floor() as i32;
        let camera_tile_z = (camera_position.z / TILE_SIZE).floor() as i32;
        let half_span = (settings.far_radius / TILE_SIZE).ceil() as i32 + 4;
        let tiles_per_side = half_span * 2 + 1;
        let total_tiles = (tiles_per_side * tiles_per_side) as u32;
        self.tile_dispatch_workgroups = total_tiles.div_ceil(64);

        let (sun_direction, sun_color) = query_sun(world);
        let ambient = world.resources.render_settings.ambient_light;

        let wind = &world.resources.wind;
        let wind_planar = nalgebra_glm::Vec2::new(wind.direction.x, wind.direction.z);
        let wind_direction = if wind_planar.norm() > f32::EPSILON {
            wind_planar.normalize()
        } else {
            nalgebra_glm::Vec2::new(1.0, 0.0)
        };

        let (finite_flag, domain_rect) = match settings.domain {
            GrassDomain::Finite {
                center,
                half_extents,
            } => (
                1u32,
                [
                    center.x - half_extents.x,
                    center.y - half_extents.y,
                    center.x + half_extents.x,
                    center.y + half_extents.y,
                ],
            ),
            GrassDomain::Infinite => (0u32, [0.0; 4]),
        };

        let mut pushers = [[0.0f32; 4]; MAX_PUSHERS];
        let mut pusher_strength = [[0.0f32; 4]; 2];
        let pusher_count = settings.pushers.len().min(MAX_PUSHERS);
        for (index, pusher) in settings.pushers.iter().take(MAX_PUSHERS).enumerate() {
            pushers[index] = [
                pusher.position.x,
                pusher.position.y,
                pusher.position.z,
                pusher.radius,
            ];
            pusher_strength[index / 4][index % 4] = pusher.strength;
        }

        let uniforms = GrassUniforms {
            view_projection: view_projection.into(),
            frustum_planes: frustum_planes.map(|plane| [plane.x, plane.y, plane.z, plane.w]),
            camera_position: [camera_position.x, camera_position.y, camera_position.z, 0.0],
            camera_right: [camera_right.x, camera_right.y, camera_right.z, 0.0],
            camera_tile: [camera_tile_x, camera_tile_z, tiles_per_side, half_span],
            sun_direction: [sun_direction.x, sun_direction.y, sun_direction.z, 0.0],
            sun_color: [sun_color.x, sun_color.y, sun_color.z, 0.0],
            ambient: [
                ambient[0] * ambient[3],
                ambient[1] * ambient[3],
                ambient[2] * ambient[3],
                0.0,
            ],
            wind: [
                wind_direction.x,
                wind_direction.y,
                wind.strength * 0.12,
                wind.turbulence.clamp(0.0, 1.0),
            ],
            radii: [
                settings.high_radius,
                settings.low_radius,
                settings.far_radius,
                settings.ground_height,
            ],
            params: [
                settings.type_noise_scale.max(1.0),
                settings.clump_scale.max(0.2),
                0.0,
                self.time,
            ],
            counts: [
                settings.types.len().min(MAX_TYPES) as u32,
                pusher_count as u32,
                finite_flag,
                u32::from(terrain_sample.is_none()),
            ],
            domain: domain_rect,
            height_map: match (&terrain_sample, &settings.height_map) {
                (Some((height_map, _)), _) => *height_map,
                (None, Some(height_map)) => [
                    height_map.origin.x,
                    height_map.origin.y,
                    height_map.world_span.max(1.0),
                    1.0,
                ],
                (None, None) => [0.0; 4],
            },
            height_bounds: match (&terrain_sample, &settings.height_map) {
                (Some((_, bounds)), _) => *bounds,
                (None, Some(height_map)) => {
                    [height_map.height_min, height_map.height_max, 0.0, 0.0]
                }
                (None, None) => [0.0; 4],
            },
            splat: terrain_splat,
            pushers,
            pusher_strength,
        };
        queue.write_buffer(&self.uniforms_buffer, 0, bytemuck::bytes_of(&uniforms));

        if self.types_revision != Some(settings.types_revision) {
            let types = gpu_types(settings);
            queue.write_buffer(&self.types_buffer, 0, bytemuck::cast_slice(&types));
            self.types_revision = Some(settings.types_revision);
        }

        queue.write_buffer(
            &self.dispatch_buffer,
            0,
            bytemuck::cast_slice(&[0u32, 1, 1, 0]),
        );
        queue.write_buffer(
            &self.draw_commands_buffer,
            0,
            bytemuck::cast_slice(&[
                HIGH_INDEX_COUNT,
                0,
                0,
                0,
                0,
                LOW_INDEX_COUNT,
                0,
                LOW_FIRST_INDEX,
                0,
                0,
                FAR_VERTEX_COUNT,
                0,
                0,
                0,
                0,
                0,
            ]),
        );
    }

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

        {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Grass Compute Pass"),
                        timestamp_writes: None,
                    });
            compute_pass.set_pipeline(&self.cull_pipeline);
            compute_pass.set_bind_group(0, &self.cull_bind_group, &[]);
            compute_pass.dispatch_workgroups(self.tile_dispatch_workgroups, 1, 1);
            compute_pass.set_pipeline(&self.generate_pipeline);
            compute_pass.set_bind_group(0, &self.generate_bind_group, &[]);
            compute_pass.dispatch_workgroups_indirect(&self.dispatch_buffer, 0);
        }

        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,
                    multiview_mask: None,
                });

            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
            render_pass.set_pipeline(&self.high_pipeline);
            render_pass.set_bind_group(0, &self.high_bind_group, &[]);
            render_pass.draw_indexed_indirect(&self.draw_commands_buffer, 0);
            render_pass.set_pipeline(&self.low_pipeline);
            render_pass.set_bind_group(0, &self.low_bind_group, &[]);
            render_pass.draw_indexed_indirect(&self.draw_commands_buffer, LOW_DRAW_OFFSET);
            render_pass.set_pipeline(&self.far_pipeline);
            render_pass.set_bind_group(0, &self.high_bind_group, &[]);
            render_pass.draw_indirect(&self.draw_commands_buffer, FAR_DRAW_OFFSET);
        }

        Ok(context.into_sub_graph_commands())
    }
}
