//! Per-draw geometry capture for the host UI's faithful-render path.
//!
//! The deterministic headless core (`check --gpu-inline`) never touches this
//! module — it is populated only when a UI bridge is installed and consumed
//! only by `crates/xenia-ui`. The goal is to hand the UI the *real* guest
//! geometry behind each `PM4_DRAW_INDX*` packet so it can rasterize the
//! actual splash vertices instead of synthetic placeholder shapes.
//!
//! What the WGSL pipeline needs to reconstruct one draw (see
//! `shaders/xenos_interp.wgsl` `vs_main` / `interpret_vertex_fetch`):
//!   * the active VS/PS blob keys (already published as assets),
//!   * the primitive type + the host vertex count to issue,
//!   * the raw guest vertex-buffer bytes for the fetched window, and
//!   * the *dword base* of that window so the shader can rebase the absolute
//!     fetch-constant address into the uploaded buffer.
//!
//! The hard part is sourcing the vertex window: the VS reads a vertex-fetch
//! constant (`xe_gpu_vertex_fetch_t`) whose dword-0 carries the absolute
//! guest dword address. We parse the active VS, find its first vertex fetch,
//! read that fetch constant out of the register file, then copy a bounded
//! window of guest memory starting at the fetch base.

use xenia_memory::access::MemoryAccess;

use crate::draw_state::{IndexSize, IndexSource, PrimitiveType};
use crate::register_file::RegisterFile;

/// Texture-fetch / vertex-fetch constant region base, in register indices.
/// Each fetch constant is 6 dwords (`xe_gpu_*_fetch_t`).
const CONST_BASE_FETCH: u32 = 0x4800;

/// Upper bound (in dwords) on the vertex window we copy per draw. The splash
/// UI draws are tiny (3–4 verts × ≤4 dwords); 64 KiB of dwords is generous
/// slack while bounding the per-frame copy cost and the 16 MiB host buffer.
const MAX_WINDOW_DWORDS: u32 = 16 * 1024;

/// One captured draw, with enough real state for the UI to replay it through
/// the existing wgpu Xenos pipeline.
#[derive(Clone, Debug)]
pub struct DrawCapture {
    /// Monotonic global draw index (matches `GpuStats::draws_seen` at capture).
    pub draw_index: u32,
    /// Xenos primitive-type code (see `SwapInfo::last_draw_prim` encoding).
    pub prim_code: u32,
    /// Host vertex count to issue (post primitive-processor rewrite).
    pub host_vertex_count: u32,
    /// Active VS blob key at draw time (0 = none).
    pub vs_key: u32,
    /// Active PS blob key at draw time (0 = none).
    pub ps_key: u32,
    /// Raw guest dwords of the fetched vertex window (host-endian as stored in
    /// guest memory — the WGSL applies the per-format endian swap). `addr 0`
    /// of this buffer corresponds to guest dword `window_base_dwords`.
    pub vertex_dwords: Vec<u32>,
    /// Guest dword address that maps to index 0 of `vertex_dwords`. The shader
    /// subtracts this from the fetch-constant base to index `vertex_dwords`.
    pub window_base_dwords: u32,
    /// `true` when we successfully resolved a real vertex window. When `false`
    /// the UI falls back to its procedural geometry for this draw (honest:
    /// nothing faked, just "couldn't source real vertices").
    pub has_real_vertices: bool,
}

/// Encode a [`PrimitiveType`] as the raw Xenos code used across the bridge.
pub fn prim_code(p: PrimitiveType) -> u32 {
    match p {
        PrimitiveType::None => 0,
        PrimitiveType::PointList => 1,
        PrimitiveType::LineList => 2,
        PrimitiveType::LineStrip => 3,
        PrimitiveType::TriangleList => 4,
        PrimitiveType::TriangleFan => 5,
        PrimitiveType::TriangleStrip => 6,
        PrimitiveType::RectangleList => 8,
        PrimitiveType::QuadList => 13,
        PrimitiveType::Unknown(x) => x as u32,
    }
}

/// Resolve the first vertex-fetch window referenced by the parsed VS.
///
/// Walks the VS instruction stream for the first `vfetch` (mini) instruction,
/// reads its fetch constant from `rf`, and copies a bounded window of guest
/// memory starting at the fetch base. Returns `(dwords, window_base_dwords)`
/// or `None` if the VS has no vertex fetch or the constant is malformed.
fn resolve_vertex_window(
    parsed_vs: &crate::ucode::ParsedShader,
    rf: &RegisterFile,
    mem: &dyn MemoryAccess,
) -> Option<(Vec<u32>, u32)> {
    // The instruction block is 3 dwords per ALU/fetch triple. We don't have
    // per-triple kind flags here, so we scan every triple and accept the
    // first one that decodes as a *vertex* fetch with a plausible constant.
    let instrs = &parsed_vs.instructions;
    let mut fetch_const: Option<u8> = None;
    let mut t = 0usize;
    while t + 2 < instrs.len() {
        let w0 = instrs[t];
        let w1 = instrs[t + 1];
        let w2 = instrs[t + 2];
        if let crate::ucode::fetch::FetchInstruction::Vertex(vf) =
            crate::ucode::fetch::decode_fetch([w0, w1, w2])
        {
            // Validate the referenced fetch constant is a real vertex fetch
            // (type==3, kVertex) before trusting it.
            let fc = vf.fetch_const as u32;
            let dword0 = rf.read(CONST_BASE_FETCH + fc * 6);
            if dword0 & 0x3 == 3 {
                fetch_const = Some(vf.fetch_const);
                break;
            }
        }
        t += 3;
    }
    let fc = fetch_const? as u32;
    let dword0 = rf.read(CONST_BASE_FETCH + fc * 6);
    let dword1 = rf.read(CONST_BASE_FETCH + fc * 6 + 1);
    // address:30 at bits[31:2] of dword0 (in bytes once masked). The fetch
    // constant carries a guest *physical* dword address — canary reads the
    // vertex buffer via `Memory::TranslatePhysical(fetch.address * 4)`
    // (`draw_util.cc:961`). On the Xbox 360 the physical range is mirrored at
    // several virtual windows; ours only maps the cached-physical window at
    // `0x4000_0000` (`gpu_system::physical_to_backing`). Reading the bare low
    // address (`0x0adf_xxxx`) hits an unmapped VA and returns zeros, so rebase
    // a low physical base onto the mapped `0x4000_0000` alias when the raw VA
    // is not itself mapped. `window_base_dwords` keeps the *original* base so
    // the shader's rebase against the (unmodified) fetch-constant address still
    // indexes the uploaded window correctly.
    let base_bytes = dword0 & 0xFFFF_FFFC;
    if base_bytes == 0 {
        return None;
    }
    let read_base = if mem.translate(base_bytes).is_some() {
        base_bytes
    } else if base_bytes < 0x2000_0000 && mem.translate(base_bytes | 0x4000_0000).is_some() {
        base_bytes | 0x4000_0000
    } else {
        base_bytes
    };
    // size:24 at bits[25:2] of dword1, in dwords. Clamp to our window cap.
    let size_dwords = ((dword1 >> 2) & 0x00FF_FFFF).clamp(1, MAX_WINDOW_DWORDS);
    let window_base_dwords = base_bytes >> 2;
    let mut dwords = Vec::with_capacity(size_dwords as usize);
    for i in 0..size_dwords {
        let addr = read_base.wrapping_add(i * 4);
        if addr < read_base {
            break; // wrap guard
        }
        // `read_u32` composes big-endian bytes into the u32 value; the WGSL's
        // `gpu_swap` expects the *raw little-endian dword* as it sits in guest
        // memory, so undo the BE composition with `swap_bytes`.
        dwords.push(mem.read_u32(addr).swap_bytes());
    }
    if dwords.is_empty() {
        return None;
    }
    Some((dwords, window_base_dwords))
}

/// Build a [`DrawCapture`] for one draw. Best-effort: when the vertex window
/// can't be resolved, `has_real_vertices` is `false` and the UI falls back to
/// procedural geometry (never fabricated pixels).
#[allow(clippy::too_many_arguments)]
pub fn build(
    draw_index: u32,
    primitive: PrimitiveType,
    host_vertex_count: u32,
    _index_source: IndexSource,
    _index_size: IndexSize,
    vs_key: u32,
    ps_key: u32,
    parsed_vs: Option<&crate::ucode::ParsedShader>,
    rf: &RegisterFile,
    mem: &dyn MemoryAccess,
) -> DrawCapture {
    let (vertex_dwords, window_base_dwords, has_real) = match parsed_vs
        .and_then(|vs| resolve_vertex_window(vs, rf, mem))
    {
        Some((d, base)) => (d, base, true),
        None => (Vec::new(), 0, false),
    };
    DrawCapture {
        draw_index,
        prim_code: prim_code(primitive),
        host_vertex_count,
        vs_key,
        ps_key,
        vertex_dwords,
        window_base_dwords,
        has_real_vertices: has_real,
    }
}
