use super::passes;

impl super::WgpuRenderer {
    pub(super) fn ensure_camera_viewport(
        &mut self,
        camera_entity: freecs::Entity,
        width: u32,
        height: u32,
    ) -> Option<egui::TextureId> {
        if let Some(viewport) = self.camera_viewports.get(&camera_entity)
            && viewport.size == (width, height)
        {
            return Some(viewport.texture_id);
        }

        let existing_texture_id = self
            .camera_viewports
            .get(&camera_entity)
            .map(|viewport| viewport.texture_id);

        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
            label: Some(&format!("Viewport Texture (Camera {})", camera_entity.id)),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: self.surface_format,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        let texture_id = if let Some(egui_pass) = self.graph.get_pass_mut("egui_pass")
            && let Some(egui_pass) =
                (egui_pass as &mut dyn std::any::Any).downcast_mut::<passes::EguiPass>()
        {
            if let Some(existing_id) = existing_texture_id {
                egui_pass.update_native_texture(&self.device, existing_id, &view);
                existing_id
            } else {
                egui_pass.register_texture(&self.device, &view)
            }
        } else {
            return None;
        };

        self.camera_viewports.insert(
            camera_entity,
            super::CameraViewport {
                texture,
                view,
                texture_id,
                size: (width, height),
            },
        );

        Some(texture_id)
    }

    pub(super) fn prepare_egui_pass(
        &mut self,
        ui_output: &egui::FullOutput,
        ui_primitives: &[egui::ClippedPrimitive],
    ) {
        if let Some(egui_pass) = self.graph.get_pass_mut("egui_pass")
            && let Some(egui_pass) =
                (egui_pass as &mut dyn std::any::Any).downcast_mut::<passes::EguiPass>()
        {
            egui_pass.update_textures(&self.device, &self.queue, &ui_output.textures_delta);
        }

        let screen_descriptor = egui_wgpu::ScreenDescriptor {
            size_in_pixels: [self.surface_config.width, self.surface_config.height],
            pixels_per_point: ui_output.pixels_per_point,
        };
        if let Some(egui_pass) = self.graph.get_pass_mut("egui_pass")
            && let Some(egui_pass) =
                (egui_pass as &mut dyn std::any::Any).downcast_mut::<passes::EguiPass>()
        {
            egui_pass.set_screen_descriptor(screen_descriptor);
            egui_pass.set_paint_jobs(ui_primitives.to_vec());
        }
    }

    pub(super) fn render_egui_to_secondary_surface_impl(
        &mut self,
        id: usize,
        ui_output: Option<egui::FullOutput>,
        ui_primitives: Option<Vec<egui::ClippedPrimitive>>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let Some(secondary) = self.secondary_surfaces.get_mut(&id) else {
            return Ok(());
        };
        let Some(egui_renderer) = secondary.egui_renderer.as_mut() else {
            return Ok(());
        };

        if let Some(egui::FullOutput {
            ref textures_delta, ..
        }) = ui_output
        {
            for (texture_id, image_delta) in &textures_delta.set {
                egui_renderer.update_texture(&self.device, &self.queue, *texture_id, image_delta);
            }
            for texture_id in &textures_delta.free {
                egui_renderer.free_texture(texture_id);
            }
        }

        let width = secondary.width;
        let height = secondary.height;

        if let Some(ref paint_jobs) = ui_primitives {
            let pixels_per_point = ui_output
                .as_ref()
                .map(|output| output.pixels_per_point)
                .unwrap_or(1.0);
            let screen_descriptor = egui_wgpu::ScreenDescriptor {
                size_in_pixels: [width, height],
                pixels_per_point,
            };

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

            egui_renderer.update_buffers(
                &self.device,
                &self.queue,
                &mut encoder,
                paint_jobs,
                &screen_descriptor,
            );

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

            let surface_texture = match secondary.surface.get_current_texture() {
                Ok(frame) => frame,
                Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
                    secondary.surface.configure(&self.device, &secondary.config);
                    secondary.surface.get_current_texture()?
                }
                Err(wgpu::SurfaceError::Timeout | wgpu::SurfaceError::Other) => {
                    return Ok(());
                }
                Err(error) => return Err(Box::new(error)),
            };

            let surface_view = surface_texture
                .texture
                .create_view(&wgpu::TextureViewDescriptor::default());

            if secondary.egui_depth_size != (width, height)
                || secondary.egui_depth_texture.is_none()
            {
                let depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
                    label: Some("Secondary Egui Depth Texture"),
                    size: wgpu::Extent3d {
                        width,
                        height,
                        depth_or_array_layers: 1,
                    },
                    mip_level_count: 1,
                    sample_count: 1,
                    dimension: wgpu::TextureDimension::D2,
                    format: wgpu::TextureFormat::Depth32Float,
                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
                    view_formats: &[],
                });
                let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
                secondary.egui_depth_texture = Some(depth_texture);
                secondary.egui_depth_view = Some(depth_view);
                secondary.egui_depth_size = (width, height);
            }

            let depth_stencil_attachment = secondary.egui_depth_view.as_ref().map(|view| {
                wgpu::RenderPassDepthStencilAttachment {
                    view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Discard,
                    }),
                    stencil_ops: None,
                }
            });

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

            egui_renderer.render(
                &mut encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Secondary Egui Pass"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: &surface_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Clear(wgpu::Color {
                                    r: 0.1,
                                    g: 0.1,
                                    b: 0.1,
                                    a: 1.0,
                                }),
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    })
                    .forget_lifetime(),
                paint_jobs,
                &screen_descriptor,
            );

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

        Ok(())
    }

    pub(super) fn initialize_secondary_egui_impl(&mut self, id: usize) {
        if let Some(secondary) = self.secondary_surfaces.get_mut(&id) {
            let renderer = egui_wgpu::Renderer::new(
                &self.device,
                secondary.format,
                egui_wgpu::RendererOptions {
                    depth_stencil_format: Some(wgpu::TextureFormat::Depth32Float),
                    msaa_samples: 1,
                    ..Default::default()
                },
            );
            secondary.egui_renderer = Some(renderer);
        }
    }
}
