//! egui ⇄ engine glue for the editor.
//!
//! The engine core stays UI-agnostic; all egui wiring lives here in the editor.
//! [`EguiLayer`] owns the [`egui_winit`] input state and the [`egui_wgpu`]
//! renderer, translates window events, and paints a built UI into the frame's
//! surface view (recorded with `LoadOp::Load`, so it composites on top of the
//! engine's clear).

use egui_wgpu::{Renderer, RendererOptions, ScreenDescriptor};
use egui_winit::State;
use oxide_engine::wgpu;
use oxide_engine::winit::event::WindowEvent;
use oxide_engine::winit::window::Window;

/// Holds the egui input state and GPU renderer for one window.
pub struct EguiLayer {
    state: State,
    renderer: Renderer,
}

impl EguiLayer {
    /// Creates the layer for `window`, building a renderer that targets the
    /// given surface format.
    pub fn new(
        window: &Window,
        device: &wgpu::Device,
        surface_format: wgpu::TextureFormat,
    ) -> Self {
        let context = egui::Context::default();
        let state = State::new(
            context,
            egui::ViewportId::ROOT,
            window,
            Some(window.scale_factor() as f32),
            None,
            None,
        );
        // Defaults: no MSAA, no depth/stencil, dithering on — matches the
        // editor's flat clear-color surface.
        let renderer = Renderer::new(device, surface_format, RendererOptions::default());
        Self { state, renderer }
    }

    /// Feeds a window event to egui. Returns `true` if egui consumed it (e.g.
    /// a click landed on a panel), so the caller can suppress its own handling.
    pub fn on_window_event(&mut self, window: &Window, event: &WindowEvent) -> bool {
        self.state.on_window_event(window, event).consumed
    }

    /// Whether the pointer is currently over a **floating** egui area — a
    /// `Window` (Preferences, Layer Names, Groups, …) or other non-background
    /// layer — rather than empty space or the background dock.
    ///
    /// The viewport is painted under a transparent dock area (background
    /// order), so a geometric "cursor inside the viewport rect" test can't tell
    /// that a floating panel is sitting on top of it. The host uses this to
    /// suppress viewport orbit/pan/zoom (and stray WASD while typing in a panel
    /// that overlaps the viewport).
    pub fn pointer_over_floating(&self) -> bool {
        let ctx = self.state.egui_ctx();
        let Some(pos) = ctx.pointer_latest_pos() else {
            return false;
        };
        ctx.layer_id_at(pos)
            .map(|layer| layer.order > egui::Order::Background)
            .unwrap_or(false)
    }

    /// Builds the UI via `build_ui` and paints it into `view`.
    ///
    /// `build_ui` receives the root [`egui::Ui`]; panels are shown inside it
    /// (egui 0.34's `show_inside` model). It may be called more than once per
    /// frame if egui needs an extra layout pass, so it must be idempotent.
    #[allow(clippy::too_many_arguments)]
    pub fn paint(
        &mut self,
        window: &Window,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        view: &wgpu::TextureView,
        size: (u32, u32),
        build_ui: impl FnMut(&mut egui::Ui),
    ) {
        let raw_input = self.state.take_egui_input(window);
        let context = self.state.egui_ctx().clone();
        let output = context.run_ui(raw_input, build_ui);
        self.state
            .handle_platform_output(window, output.platform_output);

        let primitives = context.tessellate(output.shapes, output.pixels_per_point);
        let screen = ScreenDescriptor {
            size_in_pixels: [size.0.max(1), size.1.max(1)],
            pixels_per_point: output.pixels_per_point,
        };

        for (id, delta) in &output.textures_delta.set {
            self.renderer.update_texture(device, queue, *id, delta);
        }

        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
            label: Some("oxide.editor.egui.encoder"),
        });
        // egui may emit its own command buffers (for paint callbacks); submit
        // those ahead of our pass.
        let user_buffers =
            self.renderer
                .update_buffers(device, queue, &mut encoder, &primitives, &screen);
        {
            let pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("oxide.editor.egui.pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view,
                    resolve_target: None,
                    depth_slice: None,
                    ops: wgpu::Operations {
                        // Load: keep the engine's clear; draw the UI over it.
                        load: wgpu::LoadOp::Load,
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
                multiview_mask: None,
            });
            // egui-wgpu wants a 'static pass; the encoder outlives it here.
            let mut pass = pass.forget_lifetime();
            self.renderer.render(&mut pass, &primitives, &screen);
        }

        for id in &output.textures_delta.free {
            self.renderer.free_texture(id);
        }
        queue.submit(
            user_buffers
                .into_iter()
                .chain(std::iter::once(encoder.finish())),
        );
    }
}
