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

const UI_DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;

pub struct EguiPass {
    renderer: egui_wgpu::Renderer,
    screen_descriptor: egui_wgpu::ScreenDescriptor,
    paint_jobs: Vec<egui::ClippedPrimitive>,
    depth_texture: Option<wgpu::Texture>,
    depth_view: Option<wgpu::TextureView>,
    depth_size: (u32, u32),
}

impl EguiPass {
    pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
        let renderer = egui_wgpu::Renderer::new(
            device,
            surface_format,
            egui_wgpu::RendererOptions {
                depth_stencil_format: Some(UI_DEPTH_FORMAT),
                msaa_samples: 1,
                ..Default::default()
            },
        );

        Self {
            renderer,
            screen_descriptor: egui_wgpu::ScreenDescriptor {
                size_in_pixels: [1, 1],
                pixels_per_point: 1.0,
            },
            paint_jobs: Vec::new(),
            depth_texture: None,
            depth_view: None,
            depth_size: (0, 0),
        }
    }

    fn ensure_depth_texture(&mut self, device: &wgpu::Device, width: u32, height: u32) {
        if self.depth_size == (width, height) && self.depth_texture.is_some() {
            return;
        }

        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("UI Depth Texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: UI_DEPTH_FORMAT,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

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

        self.depth_texture = Some(texture);
        self.depth_view = Some(view);
        self.depth_size = (width, height);
    }

    pub fn update_textures(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        textures_delta: &egui::TexturesDelta,
    ) {
        for (id, image_delta) in &textures_delta.set {
            self.renderer
                .update_texture(device, queue, *id, image_delta);
        }

        for id in &textures_delta.free {
            self.renderer.free_texture(id);
        }
    }

    pub fn set_paint_jobs(&mut self, paint_jobs: Vec<egui::ClippedPrimitive>) {
        self.paint_jobs = paint_jobs;
    }

    pub fn set_screen_descriptor(&mut self, screen_descriptor: egui_wgpu::ScreenDescriptor) {
        self.screen_descriptor = screen_descriptor;
    }

    pub fn register_texture(
        &mut self,
        device: &wgpu::Device,
        texture_view: &wgpu::TextureView,
    ) -> egui::TextureId {
        self.renderer
            .register_native_texture(device, texture_view, wgpu::FilterMode::Linear)
    }
}

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

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

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

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

    fn prepare(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        _world: &crate::ecs::world::World,
    ) {
        if !self.paint_jobs.is_empty() {
            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Egui Prepare Encoder"),
            });

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

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

    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 (swapchain_view, swapchain_load, swapchain_store) =
            context.get_color_attachment("swapchain")?;

        let [width, height] = self.screen_descriptor.size_in_pixels;
        self.ensure_depth_texture(context.device, width, height);

        if !self.paint_jobs.is_empty() {
            let depth_stencil_attachment =
                self.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,
                    });

            self.renderer.render(
                &mut context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Egui Pass"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: swapchain_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: swapchain_load,
                                store: swapchain_store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    })
                    .forget_lifetime(),
                &self.paint_jobs,
                &self.screen_descriptor,
            );
        }

        Ok(context.into_sub_graph_commands())
    }
}
