//! GPU rendering infrastructure.
//!
//! Stage 2 acquired a GPU ([`Gpu`]), drove a window surface ([`RenderContext`]),
//! and cleared it each frame. Stage 4 adds mesh rendering: build geometry
//! ([`Mesh`]/[`Vertex`]), upload it ([`GpuMesh`]), describe surfaces with a
//! [`Material`], place a [`Camera`], and draw through the [`ForwardRenderer`].

mod camera;
mod context;
mod forward;
mod gpu;
mod material;
mod mesh;
mod pipeline;
mod renderable;
mod ui_pass;

pub use camera::Camera;
pub use context::RenderContext;
pub use forward::{DirectionalLight, ForwardRenderer, Lighting, RenderObject, DEPTH_FORMAT};
pub use gpu::Gpu;
pub use material::Material;
pub use mesh::{GpuMesh, Mesh, Vertex};
pub use pipeline::{ClearPass, ForwardPass, FrameContext, RenderPass, RenderPipeline};
pub use renderable::{MeshRenderer, PrimitiveShape};
pub use ui_pass::{UiBatch, UiOverlayPass};

use crate::math::Color;

/// Errors produced by the rendering layer.
#[derive(Debug, thiserror::Error)]
pub enum RenderError {
    /// No GPU adapter compatible with the requested surface (or headless use)
    /// was found on this system.
    #[error("no compatible GPU adapter found: {0}")]
    NoAdapter(#[from] wgpu::RequestAdapterError),

    /// The adapter was found but refused to provide a device.
    #[error("failed to request GPU device: {0}")]
    Device(#[from] wgpu::RequestDeviceError),

    /// The window surface could not be created.
    #[error("failed to create surface: {0}")]
    CreateSurface(#[from] wgpu::CreateSurfaceError),

    /// The adapter cannot present to the created surface.
    #[error("the GPU adapter does not support presenting to this surface")]
    UnsupportedSurface,

    /// Configuring the surface raised a validation error. On some drivers a
    /// backend reports a GPU but cannot actually present to the window surface
    /// (e.g. old NVIDIA on Wayland under Vulkan); this is caught so the engine
    /// can fall back to another backend instead of aborting.
    #[error("surface configuration failed: {0}")]
    SurfaceConfigure(String),

    /// Every render backend/adapter the engine tried failed to produce a
    /// working surface — no usable GPU path on this system.
    #[error("no working render backend found (tried Vulkan/Metal/DX12, GL, and software)")]
    NoWorkingBackend,

    /// Acquiring the next frame raised a validation error — a bug in surface
    /// configuration, not a transient condition.
    #[error("surface frame acquisition failed validation")]
    SurfaceValidation,
}

/// Records and submits a render pass that clears `view` to `color`.
///
/// This is the whole of Stage 2's rendering: both the windowed
/// [`RenderContext`] and offscreen targets (e.g. tests) clear through here.
pub fn clear_view(
    device: &wgpu::Device,
    queue: &wgpu::Queue,
    view: &wgpu::TextureView,
    color: Color,
) {
    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
        label: Some("oxide.clear"),
    });
    // The pass is dropped immediately: a load-op clear with no draws is all
    // that is needed to fill the target.
    let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
        label: Some("oxide.clear.pass"),
        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
            view,
            resolve_target: None,
            depth_slice: None,
            ops: wgpu::Operations {
                load: wgpu::LoadOp::Clear(to_wgpu_color(color)),
                store: wgpu::StoreOp::Store,
            },
        })],
        depth_stencil_attachment: None,
        timestamp_writes: None,
        occlusion_query_set: None,
        multiview_mask: None,
    });
    queue.submit([encoder.finish()]);
}

/// Converts the engine's [`Color`] (linear `f32`) to a [`wgpu::Color`]
/// (linear `f64`), as used by clear operations.
pub fn to_wgpu_color(color: Color) -> wgpu::Color {
    wgpu::Color {
        r: color.r as f64,
        g: color.g as f64,
        b: color.b as f64,
        a: color.a as f64,
    }
}
