//! UI/kernel bridge construction.
//!
//! The UI side owns:
//!   - a gamepad snapshot cell (writer) + connected flag (writer),
//!   - a shared staging buffer that the CPU thread fills with the guest's
//!     frontbuffer bytes on each `VdSwap`,
//!   - an `EventLoopProxy<SwapEvent>` to wake winit with a redraw request,
//!   - a shutdown atomic the UI flips on window close.
//!
//! The kernel side gets a [`xenia_kernel::UiBridge`] made from closures over
//! the same shared state, so it can read gamepad + post swaps from the CPU
//! thread without knowing about winit.

use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Mutex;

use crossbeam_utils::atomic::AtomicCell;
use winit::event_loop::EventLoopProxy;
use xenia_gpu::draw_capture::DrawCapture;
use xenia_gpu::texture_cache::TextureKey;
use xenia_gpu::xenos_constants::XenosConstantsBlock;
use xenia_hid::GamepadState;
use xenia_kernel::{SwapInfo, UiBridge};
use xenia_memory::MemoryAccess;

/// Snapshot of the guest frontbuffer, copied out of guest memory on each
/// `VdSwap` so the UI thread can upload it without touching the CPU thread's
/// [`xenia_memory::GuestMemory`].
#[derive(Default)]
pub struct FrontbufferSnapshot {
    /// Width of the most-recently-swapped frontbuffer, in pixels.
    pub width: u32,
    /// Height in pixels.
    pub height: u32,
    /// Tightly-packed RGBA8 bytes (`width * height * 4`). Decoded from the
    /// raw guest bytes by the kernel-side closure (see [`build`]).
    pub rgba: Vec<u8>,
    /// Monotonic counter so the UI can tell new frames apart from stale
    /// re-reads.
    pub revision: u64,
    /// The most recent swap metadata. Kept alongside the bytes for HUD use.
    pub info: Option<SwapInfo>,
}

/// The UI-side half of the bridge. Installed into the [`App`](crate::app)
/// that runs on the main thread.
pub struct UiHandles {
    pub gamepad: Arc<AtomicCell<GamepadState>>,
    pub gamepad_connected: Arc<AtomicBool>,
    pub shutdown: Arc<AtomicBool>,
    pub frontbuffer: Arc<Mutex<FrontbufferSnapshot>>,
    /// Live-updated summed CPU instruction counter. The `xenia-app` run
    /// loop stores `cycle_count` into this every ~10k instructions so the
    /// HUD keeps animating even before the first `VdSwap` fires. Authoritative
    /// counter comes from scheduler HW-thread contexts; this atomic is just
    /// a cross-thread cache.
    pub instructions_counter: Arc<AtomicU64>,
    /// P3b assets: cloned by `vd_swap` via `UiBridge::publish_assets`.
    /// The UI redraw path reads them (mutex-guarded) alongside the
    /// corresponding `SwapInfo` and passes them to the Xenos pipeline.
    pub shader_blobs: Arc<Mutex<HashMap<u32, Vec<u32>>>>,
    pub xenos_constants: Arc<Mutex<XenosConstantsBlock>>,
    /// P5 primary texture — `None` means "no texture this frame, use
    /// the magenta stub"; `Some((key, bytes))` means the kernel decoded
    /// fetch-constant slot 0 into linear bytes that the UI should
    /// upload into the host cache and bind at `@group(1) @binding(0)`.
    pub primary_texture: Arc<Mutex<Option<(TextureKey, Vec<u8>)>>>,
    /// iterate-3O: the most recent frame's captured per-draw geometry. The
    /// redraw path drains this to replay real guest draws. Replaced wholesale
    /// each `VdSwap`.
    pub geometry: Arc<Mutex<Vec<DrawCapture>>>,
}

/// Swap event posted by the CPU-side `VdSwap` handler via
/// [`EventLoopProxy::send_event`] after the frontbuffer bytes have been
/// copied into [`UiHandles::frontbuffer`].
#[derive(Clone, Copy, Debug)]
pub struct SwapEvent(pub SwapInfo);

/// Build a paired ([`UiHandles`], [`UiBridge`]).
///
/// The proxy is the winit user-event injector for a freshly-constructed
/// `EventLoop<SwapEvent>`; `xenia-app` owns the event loop and keeps the
/// `UiHandles` alongside it.
pub fn build(proxy: EventLoopProxy<SwapEvent>) -> (UiHandles, UiBridge) {
    let gamepad = Arc::new(AtomicCell::new(GamepadState::default()));
    let gamepad_connected = Arc::new(AtomicBool::new(false));
    let shutdown = Arc::new(AtomicBool::new(false));
    let frontbuffer = Arc::new(Mutex::new(FrontbufferSnapshot::default()));
    let instructions_counter = Arc::new(AtomicU64::new(0));
    let shader_blobs = Arc::new(Mutex::new(HashMap::<u32, Vec<u32>>::new()));
    let xenos_constants = Arc::new(Mutex::new(XenosConstantsBlock::default()));
    let primary_texture: Arc<Mutex<Option<(TextureKey, Vec<u8>)>>> =
        Arc::new(Mutex::new(None));
    let geometry: Arc<Mutex<Vec<DrawCapture>>> = Arc::new(Mutex::new(Vec::new()));

    let kernel_bridge = UiBridge {
        gamepad: {
            let gp = Arc::clone(&gamepad);
            Arc::new(move || gp.load())
        },
        // `post_swap` now only bumps the SwapInfo + revision; the RGBA
        // bytes arrive via `publish_frontbuffer` earlier in the same
        // `VdSwap` (P4 replaced the raw-scrape path with a CPU-side
        // detile). Posting the swap event after the bytes land guarantees
        // the UI's redraw path sees the latest pixels.
        post_swap: {
            let proxy = proxy.clone();
            let fb = Arc::clone(&frontbuffer);
            Arc::new(move |info: SwapInfo, _mem: &dyn MemoryAccess| {
                if let Ok(mut lock) = fb.lock() {
                    lock.info = Some(info);
                    lock.revision = lock.revision.wrapping_add(1);
                }
                let _ = proxy.send_event(SwapEvent(info));
            })
        },
        shutdown: Arc::clone(&shutdown),
        gamepad_connected: Arc::clone(&gamepad_connected),
        instructions_counter: Arc::clone(&instructions_counter),
        publish_xenos_assets: {
            let blobs = Arc::clone(&shader_blobs);
            let consts = Arc::clone(&xenos_constants);
            Arc::new(move |new_blobs, new_consts| {
                if let Ok(mut g) = blobs.lock() {
                    *g = new_blobs;
                }
                if let Ok(mut g) = consts.lock() {
                    *g = new_consts;
                }
            })
        },
        publish_frontbuffer: {
            let fb = Arc::clone(&frontbuffer);
            Arc::new(move |w, h, bytes| {
                if let Ok(mut lock) = fb.lock() {
                    lock.width = w;
                    lock.height = h;
                    lock.rgba = bytes;
                }
            })
        },
        publish_texture: {
            let tex = Arc::clone(&primary_texture);
            Arc::new(move |payload| {
                if let Ok(mut lock) = tex.lock() {
                    *lock = payload;
                }
            })
        },
        publish_geometry: {
            let geo = Arc::clone(&geometry);
            Arc::new(move |caps| {
                if let Ok(mut lock) = geo.lock() {
                    *lock = caps;
                }
            })
        },
    };

    let handles = UiHandles {
        gamepad,
        gamepad_connected,
        shutdown,
        frontbuffer,
        instructions_counter,
        shader_blobs,
        xenos_constants,
        primary_texture,
        geometry,
    };
    (handles, kernel_bridge)
}


impl UiHandles {
    pub fn request_shutdown(&self) {
        self.shutdown.store(true, Ordering::Relaxed);
    }
}
