//! GPU context: the wgpu handles every renderer borrows from.
//!
//! This is "the wgpu stuff you need first." Before a single triangle, wgpu wants:
//! an `Instance` (the library entry point), an `Adapter` (a physical GPU), a
//! `Device` + `Queue` (your logical handle and its command-submission port), and
//! a configured `Surface` (the window's swapchain). [`GpuContext`] owns exactly
//! those and nothing about sprites or pipelines, so any renderer can be built on
//! top of it and reuse it unchanged.
//!
//! STATUS: implemented and verified against wgpu 29 on hardware (2026-07-07):
//! `new` (instance -> surface -> adapter -> device -> configured surface),
//! `resize`, and the `acquire` frame entry with its CurrentSurfaceTexture
//! match. The instance and adapter stay stored for the future XR handoff.
//!
//! The existing `wgpu_backend.rs` is the reference monolith that predates this
//! extraction; refactoring it to sit on top of GpuContext (plus a split-out
//! `sprite_pipeline.rs`) remains a planned, optional cleanup.

#![allow(dead_code)] // signatures scaffold; this lifts once the renderer uses it

use std::sync::Arc;

use winit::window::Window;

use crate::error::{MawidaError, Result};

/// Owns the core wgpu handles: the link to the GPU and the window surface we
/// present to. Held for the life of the app; a renderer borrows `device` and
/// `queue` from here every frame.
pub struct GpuContext {
    /// The wgpu entry point. Kept alive because the wgpu-hal native-handle
    /// handoff OpenXR needs starts from it (a CCF bring-up finding).
    pub(crate) instance: wgpu::Instance,
    /// The physical GPU we chose. Kept because surface capabilities and, later,
    /// the `wgpu-hal` native-handle handoff for OpenXR are reached through it.
    pub(crate) adapter: wgpu::Adapter,
    /// Logical device: creates buffers, textures, pipelines, bind groups.
    pub(crate) device: wgpu::Device,
    /// Submission port for command buffers and queued writes.
    pub(crate) queue: wgpu::Queue,
    /// The window's drawable surface (its swapchain).
    pub(crate) surface: wgpu::Surface<'static>,
    /// How the surface is configured: format, size, present mode, alpha.
    pub(crate) config: wgpu::SurfaceConfiguration,
    /// Drawable size in physical pixels.
    size: (u32, u32),
}

impl GpuContext {
    /// Bootstrap wgpu for `window`.
    ///
    /// `backends` forces a graphics API. Pass `wgpu::Backends::all()` to let wgpu
    /// pick (Metal on Apple, Vulkan/DX12 elsewhere). Pass `wgpu::Backends::VULKAN`
    /// when the VR work begins, so the program is already on Vulkan and the
    /// `wgpu-hal` escape hatch that OpenXR needs is available.
    ///
    /// Implementation steps (each is one `// VERIFY:` against the wgpu 29 docs):
    /// 1. create an `Instance` for `backends`
    /// 2. create the `Surface` from the owned `Arc<Window>`
    /// 3. `request_adapter` compatible with that surface
    /// 4. `request_device` to obtain `(device, queue)`
    /// 5. choose a surface format, build a `SurfaceConfiguration`, and apply it
    pub fn new(window: Arc<Window>, backends: wgpu::Backends) -> Result<Self> {
        // `new` stays synchronous; the async wgpu setup runs to completion here.
        pollster::block_on(Self::new_async(window, backends))
    }

    async fn new_async(window: Arc<Window>, backends: wgpu::Backends) -> Result<Self> {
        let phys = window.inner_size();
        let size = (phys.width.max(1), phys.height.max(1));

        // 1. Instance: the wgpu entry point, for the chosen backend(s).
        // wgpu 29: InstanceDescriptor has no `Default` (it can own a display
        // handle) and `Instance::new` takes it by value, not by `&`.
        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
            backends,
            ..wgpu::InstanceDescriptor::new_without_display_handle()
        });

        // 2. Surface: borrows nothing because we pass an owned `Arc<Window>`,
        // which is what lets the field be `Surface<'static>`.
        let surface = instance
            .create_surface(window.clone())
            .map_err(|e| MawidaError::Surface(e.to_string()))?;

        // 3. Adapter: a physical GPU that can present to that surface.
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                // High performance by default: prefer the discrete GPU. A
                // user-facing GPU override is planned (see `enumerate_adapters`).
                power_preference: wgpu::PowerPreference::HighPerformance,
                compatible_surface: Some(&surface),
                force_fallback_adapter: false,
            })
            .await
            .map_err(|_| MawidaError::NoAdapter)?;

        // 4. Device + Queue: one call yields the tuple; no `trace_path` in wgpu 29.
        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: Some("mawida device"),
                ..Default::default()
            })
            .await
            .map_err(|e| MawidaError::Backend(e.to_string()))?;

        // 5. Pick a non-sRGB format so our linear-ish colors display as authored
        // (switch to an sRGB surface only if you apply gamma in the shader),
        // build the configuration, and apply it.
        let caps = surface.get_capabilities(&adapter);
        let format = caps
            .formats
            .iter()
            .copied()
            .find(|f| !f.is_srgb())
            .unwrap_or(caps.formats[0]);

        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format,
            width: size.0,
            height: size.1,
            present_mode: wgpu::PresentMode::AutoVsync,
            alpha_mode: caps.alpha_modes[0],
            view_formats: vec![],
            desired_maximum_frame_latency: 2,
        };
        surface.configure(&device, &config);

        Ok(Self {
            instance,
            adapter,
            device,
            queue,
            surface,
            config,
            size,
        })
    }

    /// The wgpu instance, for native-handle interop (OpenXR via wgpu-hal).
    pub fn instance(&self) -> &wgpu::Instance {
        &self.instance
    }

    /// The physical GPU chosen at bring-up.
    pub fn adapter(&self) -> &wgpu::Adapter {
        &self.adapter
    }

    /// Reconfigure the surface after a window resize. A no-op on a zero dimension
    /// (a minimized window), which wgpu refuses to configure.
    pub fn resize(&mut self, width: u32, height: u32) {
        if width == 0 || height == 0 {
            return;
        }
        self.size = (width, height);
        self.config.width = width;
        self.config.height = height;
        self.surface.configure(&self.device, &self.config);
    }

    /// Acquire the next swapchain image to render into. `Ok(None)` means
    /// "skip this frame" (occluded or timed out; a minimized window is the
    /// common case). For VR, this is the method that becomes "acquire the
    /// compositor's image" instead.
    ///
    /// wgpu 29 returns a status ENUM, not a Result: the usable variants carry
    /// the texture, the rest say why there is none. On Outdated/Lost we
    /// reconfigure and retry once.
    pub fn acquire(&mut self) -> Result<Option<wgpu::SurfaceTexture>> {
        use wgpu::CurrentSurfaceTexture as Cst;
        match self.surface.get_current_texture() {
            Cst::Success(t) => Ok(Some(t)),
            Cst::Suboptimal(t) => {
                // Usable now; reconfiguring keeps the next frame optimal.
                self.surface.configure(&self.device, &self.config);
                Ok(Some(t))
            }
            Cst::Outdated | Cst::Lost => {
                self.surface.configure(&self.device, &self.config);
                match self.surface.get_current_texture() {
                    Cst::Success(t) | Cst::Suboptimal(t) => Ok(Some(t)),
                    _ => Ok(None),
                }
            }
            Cst::Timeout | Cst::Occluded => Ok(None),
            Cst::Validation => Err(MawidaError::Surface(
                "validation error acquiring the frame".into(),
            )),
        }
    }

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