//! The [`WindowApp`] trait and per-callback context.

use winit::event::WindowEvent;
use winit::window::Window;

use crate::input::InputState;
use crate::math::Color;
use crate::render::{Gpu, RenderContext};

/// An application driven by the engine's event loop.
///
/// Implement this and pass the value to [`run`](super::run). All methods have
/// empty defaults so minimal apps only override what they need. Per frame the
/// engine calls [`event`](Self::event) for each pending window event, then
/// [`update`](Self::update), then clears and presents the surface.
///
/// This trait is the **window-event handler** — the per-frame plumbing between
/// `winit` and a renderer. It is distinct from the engine's
/// [`App`](crate::app::App) **container**, which owns the scene, assets, and
/// scheduled systems. The editor's main loop typically implements this trait
/// on a struct that *also* owns an `oxide_engine::app::App`.
pub trait WindowApp {
    /// Called once, after the window and GPU context exist but before the
    /// first frame.
    fn init(&mut self, ctx: &mut AppCtx<'_>) {
        let _ = ctx;
    }

    /// Called for every raw window event (keyboard, mouse, resize, focus, …).
    ///
    /// Events the engine itself reacts to (close request, resize) are still
    /// forwarded here afterwards, so apps observe everything.
    fn event(&mut self, ctx: &mut AppCtx<'_>, event: &WindowEvent) {
        let _ = (ctx, event);
    }

    /// Called once per frame, before the frame is rendered.
    fn update(&mut self, ctx: &mut AppCtx<'_>) {
        let _ = ctx;
    }

    /// Called each frame after the surface has been cleared and before it is
    /// presented, so the app can record its own draw commands into the frame.
    ///
    /// This is the hook editor/overlay UI (egui) and, in later stages, the
    /// scene renderer draw through. The surface is cleared with
    /// [`LoadOp::Clear`](wgpu::LoadOp::Clear) *before* this runs; record passes
    /// here with [`LoadOp::Load`](wgpu::LoadOp::Load) to draw on top of the
    /// clear color rather than wiping it.
    fn render(&mut self, ctx: &RenderCtx<'_>) {
        let _ = ctx;
    }
}

/// Per-frame rendering context passed to [`WindowApp::render`].
///
/// Unlike [`AppCtx`], this borrows the GPU and surface immutably: by the time
/// the draw hook runs the frame's surface texture is already acquired, so the
/// app receives the handles it needs to record additional passes into
/// [`view`](Self::view) without re-entering the render context.
pub struct RenderCtx<'a> {
    /// The GPU device/queue to record and submit commands with.
    pub gpu: &'a Gpu,
    /// The current frame's surface texture view (the render target).
    pub view: &'a wgpu::TextureView,
    /// The window being rendered, e.g. for input/UI integration that needs it.
    pub window: &'a Window,
    /// The surface's texture format, needed to build matching pipelines.
    pub surface_format: wgpu::TextureFormat,
    /// Surface size in physical pixels (`width`, `height`).
    pub size: (u32, u32),
}

/// Engine state handed to every [`WindowApp`] callback.
pub struct AppCtx<'a> {
    pub(crate) render: &'a mut RenderContext,
    pub(crate) window: &'a Window,
    pub(crate) exit: &'a mut bool,
    pub(crate) input: &'a InputState,
    /// Seconds elapsed since the previous frame (`0.0` during
    /// [`App::init`] and the first frame).
    pub dt: f32,
}

impl AppCtx<'_> {
    /// The render context driving the window surface.
    pub fn render(&mut self) -> &mut RenderContext {
        self.render
    }

    /// Sets the color the surface is cleared to, effective next frame.
    pub fn set_clear_color(&mut self, color: Color) {
        self.render.set_clear_color(color);
    }

    /// The current clear color.
    pub fn clear_color(&self) -> Color {
        self.render.clear_color()
    }

    /// Current surface size in physical pixels.
    pub fn size(&self) -> (u32, u32) {
        self.render.size()
    }

    /// Sets the window title.
    pub fn set_title(&self, title: &str) {
        self.window.set_title(title);
    }

    /// The window being driven, e.g. to construct UI/input integration that
    /// needs a window handle.
    pub fn window(&self) -> &Window {
        self.window
    }

    /// Asks the event loop to exit after the current callback returns.
    pub fn request_exit(&mut self) {
        *self.exit = true;
    }

    /// The per-frame input snapshot.
    ///
    /// Reflects every keyboard / mouse / scroll event delivered since the
    /// previous frame's `update` returned. In [`WindowApp::event`] callbacks
    /// it includes the event currently being delivered (the runner pumps it
    /// before invoking the callback). In [`WindowApp::update`] it is the
    /// accumulated state for the new frame; the runner clears edges (pressed/
    /// released, mouse delta, scroll) automatically after `update` returns.
    pub fn input(&self) -> &InputState {
        self.input
    }
}
