use glam::Mat4;

use super::types::{DrawCall, Pipeline, Uniforms, MAX_SPOTLIGHTS};
use super::Renderer;

impl Renderer
{
    pub(super) fn render_shadow_pass(
        &mut self,
        draw_calls: &[DrawCall],
        light_view_projections: &[Mat4],
        player_position: glam::Vec3,
        time: f32,
    )
    {
        let spotlight_count = self.spotlights.len().min(MAX_SPOTLIGHTS);

        for layer in 0..spotlight_count
        {
            let shadow_uniforms = Uniforms::new(
                Mat4::IDENTITY,
                Mat4::IDENTITY,
                light_view_projections[layer],
                light_view_projections,
                glam::Vec3::ZERO,
                player_position,
                self.terrain_height_scale,
                time,
                self.shadow_bias,
                2.0,
                false,
                false,
                &self.spotlights,
                0,
            );
            self.queue.write_buffer(
                &self.uniform_buffer,
                0,
                bytemuck::cast_slice(&[shadow_uniforms]),
            );

            let layer_view = self
                .shadow_map_texture
                .create_view(&wgpu::TextureViewDescriptor {
                    dimension: Some(wgpu::TextureViewDimension::D2),
                    base_array_layer: layer as u32,
                    array_layer_count: Some(1),
                    ..Default::default()
                });

            let mut encoder = self
                .device
                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                    label: Some("Shadow Pass Encoder"),
                });

            {
                let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("Shadow Pass"),
                    color_attachments: &[],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: &layer_view,
                        depth_ops: Some(wgpu::Operations {
                            load: wgpu::LoadOp::Clear(1.0),
                            store: wgpu::StoreOp::Store,
                        }),
                        stencil_ops: None,
                    }),
                    timestamp_writes: None,
                    occlusion_query_set: None,
                    multiview_mask: None,
                });

                shadow_pass.set_pipeline(
                    self.shadow_pipeline
                        .as_ref()
                        .expect("shadow pipeline missing"),
                );
                shadow_pass.set_bind_group(
                    0,
                    self.shadow_bind_group
                        .as_ref()
                        .expect("shadow bind group missing"),
                    &[],
                );

                for draw_call in draw_calls.iter()
                {
                    if matches!(draw_call.pipeline, Pipeline::DebugLines)
                    {
                        continue;
                    }

                    shadow_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));

                    if let Some(ref instance_buffer) = draw_call.instance_buffer
                    {
                        shadow_pass.set_vertex_buffer(1, instance_buffer.slice(..));
                    }

                    shadow_pass.set_index_buffer(
                        draw_call.index_buffer.slice(..),
                        wgpu::IndexFormat::Uint32,
                    );
                    shadow_pass.draw_indexed(
                        0..draw_call.num_indices,
                        0,
                        0..draw_call.num_instances,
                    );
                }
            }

            self.queue.submit(std::iter::once(encoder.finish()));
        }
    }

    pub(super) fn calculate_light_view_projections(&self) -> Vec<Mat4>
    {
        self.spotlights
            .iter()
            .take(MAX_SPOTLIGHTS)
            .map(|spotlight| {
                let light_position = spotlight.position;
                let light_target = spotlight.position + spotlight.direction;

                let up_vector = if spotlight.direction.y.abs() > 0.99
                {
                    glam::Vec3::Z
                }
                else
                {
                    glam::Vec3::Y
                };

                let light_view = Mat4::look_at_rh(light_position, light_target, up_vector);

                let fov = spotlight.outer_angle * 2.0;
                let near = 0.1;
                let far = 150.0;
                let light_projection = Mat4::perspective_rh(fov, 1.0, near, far);

                light_projection * light_view
            })
            .collect()
    }
}
