use crate::ecs::graphics::Atmosphere;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};

pub struct SkyPass {
    uniform_buffer: wgpu::Buffer,
    procedural_bind_group: wgpu::BindGroup,
    sky_pipeline: wgpu::RenderPipeline,
    cloudy_sky_pipeline: wgpu::RenderPipeline,
    space_pipeline: wgpu::RenderPipeline,
    nebula_pipeline: wgpu::RenderPipeline,
    sunset_pipeline: wgpu::RenderPipeline,
    nighttime_pipeline: wgpu::RenderPipeline,
    hdr_bind_group: wgpu::BindGroup,
    hdr_pipeline: wgpu::RenderPipeline,
    hdr_lod_pipeline: wgpu::RenderPipeline,
    cubemap_texture: wgpu::Texture,
    cubemap_view: wgpu::TextureView,
    sampler: wgpu::Sampler,
    hdr_loaded: bool,
    hdr_bind_group_layout: wgpu::BindGroupLayout,
    irradiance_bind_group: wgpu::BindGroup,
    prefilter_bind_group: wgpu::BindGroup,
    irradiance_texture: Option<wgpu::Texture>,
    prefilter_texture: Option<wgpu::Texture>,
}

impl SkyPass {
    pub fn new(
        device: &wgpu::Device,
        color_format: wgpu::TextureFormat,
        _depth_format: wgpu::TextureFormat,
    ) -> Self {
        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Sky Uniform Buffer"),
            size: std::mem::size_of::<SkyUniform>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let (cubemap_texture, cubemap_view) =
            crate::render::wgpu::hdr::create_dummy_cubemap(device);

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

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

        let procedural_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &procedural_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: uniform_buffer.as_entire_binding(),
            }],
            label: Some("Sky Procedural Bind Group"),
        });

        let hdr_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Sky HDR Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::VERTEX_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::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::Cube,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });

        let hdr_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &hdr_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
            label: Some("Sky HDR Bind Group"),
        });

        let sky_shader = device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky.wgsl"));
        let cloudy_sky_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_cloudy.wgsl"));
        let space_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_space.wgsl"));
        let nebula_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_nebula.wgsl"));
        let sunset_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_sunset.wgsl"));
        let nighttime_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_nighttime.wgsl"));
        let hdr_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_hdr.wgsl"));

        let procedural_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Sky Procedural Pipeline Layout"),
                bind_group_layouts: &[&procedural_bind_group_layout],
                push_constant_ranges: &[],
            });

        let hdr_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Sky HDR Pipeline Layout"),
            bind_group_layouts: &[&hdr_bind_group_layout],
            push_constant_ranges: &[],
        });

        let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Sky Pipeline"),
            layout: Some(&procedural_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &sky_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &sky_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let cloudy_sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Cloudy Sky Pipeline"),
            layout: Some(&procedural_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &cloudy_sky_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &cloudy_sky_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let space_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Space Pipeline"),
            layout: Some(&procedural_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &space_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &space_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let nebula_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Nebula Pipeline"),
            layout: Some(&procedural_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &nebula_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &nebula_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let sunset_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Sunset Pipeline"),
            layout: Some(&procedural_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &sunset_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &sunset_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let nighttime_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Nighttime Pipeline"),
            layout: Some(&procedural_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &nighttime_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &nighttime_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let hdr_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Sky HDR Pipeline"),
            layout: Some(&hdr_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &hdr_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &hdr_shader,
                entry_point: Some("fs_sky"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let hdr_lod_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Sky HDR LOD Pipeline"),
            layout: Some(&hdr_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &hdr_shader,
                entry_point: Some("vs_sky"),
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &hdr_shader,
                entry_point: Some("fs_sky_lod"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let irradiance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &hdr_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
            label: Some("Sky Irradiance Bind Group"),
        });

        let prefilter_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &hdr_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
            label: Some("Sky Prefilter Bind Group"),
        });

        Self {
            uniform_buffer,
            procedural_bind_group,
            sky_pipeline,
            cloudy_sky_pipeline,
            space_pipeline,
            nebula_pipeline,
            sunset_pipeline,
            nighttime_pipeline,
            hdr_bind_group,
            hdr_pipeline,
            hdr_lod_pipeline,
            cubemap_texture,
            cubemap_view,
            sampler,
            hdr_loaded: false,
            hdr_bind_group_layout,
            irradiance_bind_group,
            prefilter_bind_group,
            irradiance_texture: None,
            prefilter_texture: None,
        }
    }

    pub fn load_hdr_skybox(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        bytes: &[u8],
    ) -> (wgpu::TextureView, wgpu::TextureView) {
        let result = crate::render::wgpu::hdr::load_hdri_texture(device, queue, bytes);

        self.cubemap_texture = result.cubemap;
        self.cubemap_view = result.cubemap_view;
        self.hdr_loaded = true;

        let irradiance_view = result.filtered_maps.irradiance_view;
        let prefiltered_view = result.filtered_maps.prefiltered_view;

        self.irradiance_texture = Some(result.filtered_maps.irradiance_texture);
        self.prefilter_texture = Some(result.filtered_maps.prefiltered_texture);

        let irradiance_view_for_sky =
            self.irradiance_texture
                .as_ref()
                .unwrap()
                .create_view(&wgpu::TextureViewDescriptor {
                    dimension: Some(wgpu::TextureViewDimension::Cube),
                    ..Default::default()
                });

        let prefilter_view_for_sky =
            self.prefilter_texture
                .as_ref()
                .unwrap()
                .create_view(&wgpu::TextureViewDescriptor {
                    dimension: Some(wgpu::TextureViewDimension::Cube),
                    ..Default::default()
                });

        self.hdr_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.hdr_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&self.cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::Sampler(&self.sampler),
                },
            ],
            label: Some("Sky HDR Bind Group"),
        });

        self.irradiance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.hdr_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&irradiance_view_for_sky),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::Sampler(&self.sampler),
                },
            ],
            label: Some("Sky Irradiance Bind Group"),
        });

        self.prefilter_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.hdr_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&prefilter_view_for_sky),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::Sampler(&self.sampler),
                },
            ],
            label: Some("Sky Prefilter Bind Group"),
        });

        (irradiance_view, prefiltered_view)
    }
}

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

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

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

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

    fn prepare(
        &mut self,
        _device: &wgpu::Device,
        queue: &wgpu::Queue,
        world: &crate::ecs::world::World,
    ) {
        if let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        {
            let atmosphere = world.resources.graphics.atmosphere;
            let uniform = SkyUniform::new(
                camera_matrices.projection,
                camera_matrices.view,
                camera_matrices.camera_position,
                world.resources.window.timing.uptime_milliseconds as f32 / 1000.0,
                atmosphere.mip_level(),
            );

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

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        let atmosphere = context.configs.resources.graphics.atmosphere;
        if atmosphere == Atmosphere::None {
            return Ok(context.into_sub_graph_commands());
        }

        let (color_view, color_load, color_store) = context.get_color_attachment("color")?;

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

        match atmosphere {
            Atmosphere::None => unreachable!(),
            Atmosphere::Hdr if self.hdr_loaded => {
                render_pass.set_pipeline(&self.hdr_pipeline);
                render_pass.set_bind_group(0, &self.hdr_bind_group, &[]);
            }
            Atmosphere::Sky | Atmosphere::Hdr => {
                render_pass.set_pipeline(&self.sky_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
            Atmosphere::Space => {
                render_pass.set_pipeline(&self.space_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
            Atmosphere::Nebula => {
                render_pass.set_pipeline(&self.nebula_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
            Atmosphere::Sunset => {
                render_pass.set_pipeline(&self.sunset_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
            Atmosphere::Nighttime => {
                render_pass.set_pipeline(&self.nighttime_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
            Atmosphere::CloudySky => {
                render_pass.set_pipeline(&self.cloudy_sky_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
            Atmosphere::Irradiance if self.hdr_loaded => {
                render_pass.set_pipeline(&self.hdr_pipeline);
                render_pass.set_bind_group(0, &self.irradiance_bind_group, &[]);
            }
            Atmosphere::PrefilterMip0
            | Atmosphere::PrefilterMip1
            | Atmosphere::PrefilterMip2
            | Atmosphere::PrefilterMip3
            | Atmosphere::PrefilterMip4
                if self.hdr_loaded =>
            {
                render_pass.set_pipeline(&self.hdr_lod_pipeline);
                render_pass.set_bind_group(0, &self.prefilter_bind_group, &[]);
            }
            Atmosphere::HdrMip0
            | Atmosphere::HdrMip1
            | Atmosphere::HdrMip2
            | Atmosphere::HdrMip3
            | Atmosphere::HdrMip4
                if self.hdr_loaded =>
            {
                render_pass.set_pipeline(&self.hdr_lod_pipeline);
                render_pass.set_bind_group(0, &self.hdr_bind_group, &[]);
            }
            Atmosphere::Irradiance
            | Atmosphere::PrefilterMip0
            | Atmosphere::PrefilterMip1
            | Atmosphere::PrefilterMip2
            | Atmosphere::PrefilterMip3
            | Atmosphere::PrefilterMip4
            | Atmosphere::HdrMip0
            | Atmosphere::HdrMip1
            | Atmosphere::HdrMip2
            | Atmosphere::HdrMip3
            | Atmosphere::HdrMip4 => {
                render_pass.set_pipeline(&self.sky_pipeline);
                render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
            }
        }
        render_pass.draw(0..3, 0..1);

        drop(render_pass);

        Ok(context.into_sub_graph_commands())
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SkyUniform {
    proj: [[f32; 4]; 4],
    proj_inv: [[f32; 4]; 4],
    view: [[f32; 4]; 4],
    cam_pos: [f32; 4],
    time: f32,
    mip_level: f32,
    _padding: [f32; 2],
}

impl SkyUniform {
    pub fn new(
        projection: crate::ecs::world::Mat4,
        view: crate::ecs::world::Mat4,
        camera_position: crate::ecs::world::Vec3,
        time: f32,
        mip_level: f32,
    ) -> Self {
        let proj: [[f32; 4]; 4] = projection.into();
        let proj_inv: [[f32; 4]; 4] = nalgebra_glm::inverse(&projection).into();
        let view: [[f32; 4]; 4] = view.into();
        let cam_pos = [camera_position.x, camera_position.y, camera_position.z, 1.0];

        Self {
            proj,
            proj_inv,
            view,
            cam_pos,
            time,
            mip_level,
            _padding: [0.0; 2],
        }
    }
}
