//! Windows virtual Xbox 360 gamepad via the punktfunk **XUSB companion** UMDF driver
//! (`packaging/windows/drivers/pf-xusb`) — the in-tree replacement for ViGEmBus. One virtual Xbox 360
//! controller per client pad index, visible to classic **XInput** (`XInputGetState`) with no kernel
//! bus driver: each pad `SwDeviceCreate`s a `pf_xusb_<index>` devnode (the driver loads on it and
//! registers `GUID_DEVINTERFACE_XUSB`) and the host pushes the XInput state into an **unnamed** shared
//! DATA section the driver reaches over the **sealed channel** ([`PadChannel`] — handle duplicated
//! into its WUDFHost, bootstrapped via `Global\pfxusb-boot-<index>`; see
//! `design/gamepad-channel-sealing.md`). GameStream/Moonlight already speak the XInput conventions
//! (low-16 button bits, sticks −32768..32767 +Y up, triggers 0..255), so the state copy is ~1:1.
//!
//! Rumble flows back the other way: a game writes force-feedback via `XInputSetState`, the driver
//! parses the `SET_STATE` packet into the shared section, and [`GamepadManager::pump_rumble`] relays
//! level changes to the client (the universal 0xCA plane), mirroring the Linux `EV_FF` read path.

use super::gamepad_raii::{sw_create_cb, PadChannel, SwCreateCtx};
use crate::pad_slots::PadSlots;
use anyhow::{anyhow, Result};
use punktfunk_core::input::{GamepadEvent, MAX_PADS};
use std::ffi::c_void;
use std::sync::atomic::{fence, AtomicU32, Ordering};
use std::time::{Duration, Instant};
use windows::core::{w, GUID, PCWSTR};
use windows::Win32::Devices::Enumeration::Pnp::{
    SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO,
};
use windows::Win32::Foundation::{CloseHandle, E_FAIL, WAIT_OBJECT_0};
use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObject};

// Shared-section layout — the single source of truth is `pf_driver_proto::gamepad::XusbShm` (offset
// asserts pin every field; the `pf_xusb` driver maps the same struct). Derive the size/offsets/magic from
// it so a layout change is a compile error, not a hand-synced literal (audit §6.1).
use pf_driver_proto::gamepad::XusbShm;
const SHM_SIZE: usize = core::mem::size_of::<XusbShm>();
const SHM_MAGIC: u32 = pf_driver_proto::gamepad::XUSB_MAGIC; // "PFXU"
const OFF_PACKET: usize = core::mem::offset_of!(XusbShm, packet);
const OFF_BUTTONS: usize = core::mem::offset_of!(XusbShm, buttons);
const OFF_LT: usize = core::mem::offset_of!(XusbShm, left_trigger);
const OFF_RT: usize = core::mem::offset_of!(XusbShm, right_trigger);
const OFF_LX: usize = core::mem::offset_of!(XusbShm, thumb_lx);
const OFF_LY: usize = core::mem::offset_of!(XusbShm, thumb_ly);
const OFF_RX: usize = core::mem::offset_of!(XusbShm, thumb_rx);
const OFF_RY: usize = core::mem::offset_of!(XusbShm, thumb_ry);
const OFF_RUMBLE_SEQ: usize = core::mem::offset_of!(XusbShm, rumble_seq);
const OFF_RUMBLE: usize = core::mem::offset_of!(XusbShm, rumble_large); // large @28, small @29
const OFF_DRIVER_PROTO: usize = core::mem::offset_of!(XusbShm, driver_proto);
const OFF_PAD_INDEX: usize = core::mem::offset_of!(XusbShm, pad_index);

/// Spawn the `pf_xusb_<index>` companion devnode (hardware id `pf_xusb`, enumerator `punktfunk`). The
/// INF (System class) binds our UMDF driver, which registers the XUSB interface. Unlike the HID pads,
/// no USB compatible-ids are needed — XInput finds the device by the interface GUID, not VID/PID — but
/// we still pass a deterministic non-null `pContainerId` (the null-sentinel trips an `xinput1_4`
/// slot-skip bug). `SwDeviceClose` removes it on drop.
fn create_swdevice(index: u8) -> Result<(HSWDEVICE, Option<String>)> {
    let hwids: Vec<u16> = "pf_xusb".encode_utf16().chain([0u16, 0u16]).collect();
    let instid: Vec<u16> = format!("pf_xusb_{index}")
        .encode_utf16()
        .chain(std::iter::once(0))
        .collect();
    let desc: Vec<u16> = "Punktfunk Virtual Xbox 360 (XUSB)"
        .encode_utf16()
        .chain(std::iter::once(0))
        .collect();
    // The pad index, stamped into the device Location — the driver reads it to poll `pfxusb-boot-<index>`
    // (multi-pad). The buffer must outlive the SwDeviceCreate call (it does; we wait on the event).
    let loc: Vec<u16> = format!("{index}")
        .encode_utf16()
        .chain(std::iter::once(0))
        .collect();
    let container = GUID::from_values(0x5046_5855, 0x0000, 0x0000, [0, 0, 0, 0, 0, 0, 0, index]);

    // SAFETY: zeroed then the fields we use are set; the buffers + container outlive the call.
    let mut info: SW_DEVICE_CREATE_INFO = unsafe { std::mem::zeroed() };
    info.cbSize = std::mem::size_of::<SW_DEVICE_CREATE_INFO>() as u32;
    info.pszInstanceId = PCWSTR(instid.as_ptr());
    info.pszzHardwareIds = PCWSTR(hwids.as_ptr());
    info.pContainerId = &container;
    info.pszDeviceDescription = PCWSTR(desc.as_ptr());
    info.pszDeviceLocation = PCWSTR(loc.as_ptr());
    info.CapabilityFlags = 0x0000_000B; // DriverRequired | SilentInstall | Removable

    // SAFETY: a manual-reset, initially-unsignaled, unnamed event.
    let event = unsafe { CreateEventW(None, true, false, PCWSTR::null())? };
    // `result` starts as E_FAIL, NOT S_OK: if the wait below times out, a zero-initialised HRESULT
    // would read as success and mask the failure (found by the 2026-07 driver-health audit).
    // HEAP-allocated for the same reason as the DualSense sibling: the callback writes through this
    // pointer and SetEvents, and the wait below is bounded — a stack context would be popped while a
    // late callback still holds it. On the timeout path the box is deliberately leaked and the event
    // left open so a late write/SetEvent always targets live memory/handle.
    let ctx = Box::into_raw(Box::new(SwCreateCtx {
        event,
        result: E_FAIL,
        instance_id: [0; 128],
    }));
    // SAFETY: info + buffers outlive the call; `ctx` is a live heap allocation outliving every path.
    let hsw = match unsafe {
        SwDeviceCreate(
            w!("punktfunk"),
            w!("HTREE\\ROOT\\0"),
            &info,
            None,
            Some(sw_create_cb),
            Some(ctx as *const c_void),
        )
    } {
        Ok(h) => h,
        Err(e) => {
            // SAFETY: the call failed, so no callback is pending and `ctx` is ours to reclaim.
            unsafe {
                drop(Box::from_raw(ctx));
                let _ = CloseHandle(event);
            }
            return Err(anyhow!("SwDeviceCreate(pf_xusb) failed: {e}"));
        }
    };
    // SAFETY: event valid; block until PnP finishes enumerating, then check the callback result.
    let wait = unsafe { WaitForSingleObject(event, 10_000) };
    if wait != WAIT_OBJECT_0 {
        // Timed out — intentionally leak `ctx` and leave `event` open (see above).
        // SAFETY: hsw is the handle SwDeviceCreate returned.
        unsafe { SwDeviceClose(hsw) };
        return Err(anyhow!(
            "SwDeviceCreate(pf_xusb) enumeration callback never fired (10s) — PnP may be wedged"
        ));
    }
    // The callback ran (it signalled the event), so nothing else will touch `ctx`/`event`.
    // SAFETY: `ctx` came from `Box::into_raw` and is reclaimed exactly once here.
    let ctx = unsafe {
        let _ = CloseHandle(event);
        Box::from_raw(ctx)
    };
    if ctx.result.is_err() {
        // SAFETY: hsw is the handle SwDeviceCreate returned.
        unsafe { SwDeviceClose(hsw) };
        return Err(anyhow!(
            "SwDeviceCreate(pf_xusb) enumeration failed: {:?}",
            ctx.result
        ));
    }
    Ok((hsw, ctx.instance_id()))
}

/// A single virtual Xbox 360 pad: the `pf_xusb_<index>` devnode plus the sealed shared-memory channel.
struct XusbWinPad {
    /// Owns the `pf_xusb_<index>` devnode (dropped → `SwDeviceClose`). `None` if `SwDeviceCreate` failed.
    _sw: Option<super::gamepad_raii::SwDevice>,
    /// The sealed channel: the unnamed DATA section (the `XusbShm`) + the bootstrap mailbox + the
    /// handle-delivery state machine (drop closes both sections).
    channel: PadChannel,
    /// Watches the section's `driver_proto` field and logs attach / never-attached diagnosis.
    attach: super::gamepad_raii::DriverAttach,
    packet: u32,
    last_rumble_seq: u32,
}

impl XusbWinPad {
    /// Create the sealed channel (unnamed DATA section + `Global\pfxusb-boot-<index>` mailbox), stamp
    /// the pad index then the magic LAST, spawn the devnode, and eagerly deliver the DATA handle once
    /// the driver publishes its pid.
    fn open(index: u8) -> Result<XusbWinPad> {
        let boot_name = pf_driver_proto::gamepad::xusb_boot_name(index);
        let mut channel = PadChannel::create(boot_name.clone(), SHM_SIZE)?;
        let base = channel.data_base();
        // The section arrives zeroed; stamp the pad index (the driver validates it against its own
        // devnode index on attach) then the magic LAST (the driver only accepts it once magic is set).
        // SAFETY: base points at SHM_SIZE writable bytes; OFF_PAD_INDEX is in range.
        unsafe {
            std::ptr::write_unaligned(base.add(OFF_PAD_INDEX) as *mut u32, index as u32);
            std::ptr::write_unaligned(base as *mut u32, SHM_MAGIC);
        }
        // Propagate a devnode-create failure instead of swallowing it: a swallowed failure left the
        // pad with no devnode yet still reported success, so PadSlots latched a phantom pad (never
        // re-created for the session's life) and the host logged a misleading "virtual Xbox 360
        // created". Returning Err routes it through PadSlots' ERROR + capped-backoff retry — parity
        // with the Linux uinput path, which self-heals for exactly this reason.
        let (hsw, instance_id) = create_swdevice(index)?;
        channel.bind_devnode(
            index as u32,
            instance_id.clone(),
            super::gamepad_raii::ProofTransport::XusbIoctl,
        );
        let _sw = Some(super::gamepad_raii::SwDevice::new(hsw));
        // Bounded eager delivery: the driver's EvtDeviceAdd publishes its pid right away; handing it
        // the DATA handle before we return means the pad is live for the game's first XInput poll.
        // On a missing/old driver this waits out the window once and the service pump takes over.
        channel.deliver_eager(Duration::from_millis(1500));
        Ok(XusbWinPad {
            _sw,
            channel,
            attach: super::gamepad_raii::DriverAttach::new(
                "pf_xusb",
                "pf_xusb.inf",
                "C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Local\\Temp\\pfxusb-driver.log",
                boot_name,
                instance_id,
            ),
            packet: 0,
            last_rumble_seq: 0,
        })
    }

    /// Publish the XInput state to the section and bump the packet number (XInput uses it to detect
    /// change). `buttons` is the XINPUT_GAMEPAD_* bitmap; sticks are i16, triggers u8.
    #[allow(clippy::too_many_arguments)]
    fn write_state(&mut self, buttons: u16, lt: u8, rt: u8, lx: i16, ly: i16, rx: i16, ry: i16) {
        self.packet = self.packet.wrapping_add(1);
        let base = self.channel.data_base();
        // SAFETY: `base` is the start of the mapped section (`SHM_SIZE` bytes, owned by `Shm`); every
        // `OFF_*` is a fixed in-range offset into it and `write_unaligned` handles the unaligned field
        // writes. Single owner (`&mut self`), so no concurrent writer races these stores. `packet` (the
        // field XInput reads to detect a new state) is published LAST: the `Release` fence orders the
        // state-body stores above before the `Release` `AtomicU32` store of `packet`, so the driver —
        // which `Acquire`-loads `packet` — never observes a bumped packet over a torn body on a
        // weakly-ordered core (ARM64). On x86-TSO both are plain stores. `OFF_PACKET` (== 4) is
        // 4-aligned off the page-aligned section base, so the `AtomicU32` view is valid (mirrors the
        // seq-fenced publish in `gamepad_raii::PadChannel::create`).
        unsafe {
            std::ptr::write_unaligned(base.add(OFF_BUTTONS) as *mut u16, buttons);
            *base.add(OFF_LT) = lt;
            *base.add(OFF_RT) = rt;
            std::ptr::write_unaligned(base.add(OFF_LX) as *mut i16, lx);
            std::ptr::write_unaligned(base.add(OFF_LY) as *mut i16, ly);
            std::ptr::write_unaligned(base.add(OFF_RX) as *mut i16, rx);
            std::ptr::write_unaligned(base.add(OFF_RY) as *mut i16, ry);
            fence(Ordering::Release);
            (*(base.add(OFF_PACKET) as *const AtomicU32)).store(self.packet, Ordering::Release);
        }
    }

    /// Poll the section for a game's rumble (the driver bumps `rumble_seq` on each SET_STATE). Returns
    /// `(large, small)` motor levels (0..=255) when a new one arrived. Also ticks the sealed-channel
    /// delivery (a late-binding driver gets its handle here) and feeds the driver-attach health
    /// watcher (the driver stamps `driver_proto` once it maps the delivered section + per IOCTL).
    fn service(&mut self) -> Option<(u8, u8)> {
        self.channel.pump();
        let base = self.channel.data_base();
        // SAFETY: base points at SHM_SIZE bytes.
        let proto = unsafe { std::ptr::read_unaligned(base.add(OFF_DRIVER_PROTO) as *const u32) };
        self.attach.observe(proto);
        // SAFETY: base points at SHM_SIZE bytes; `OFF_RUMBLE_SEQ` (== 24) is 4-aligned off the
        // page-aligned base, so the `AtomicU32` view is valid. The driver bumps `rumble_seq` AFTER
        // writing the rumble bytes, so an `Acquire` load here orders the `rumble_large`/`rumble_small`
        // reads below after it — a fresh seq guarantees a coherent snapshot of the rumble bytes on a
        // weakly-ordered core (ARM64). On x86-TSO it is a plain load.
        let seq =
            unsafe { (*(base.add(OFF_RUMBLE_SEQ) as *const AtomicU32)).load(Ordering::Acquire) };
        if seq == self.last_rumble_seq {
            return None;
        }
        self.last_rumble_seq = seq;
        // SAFETY: rumble bytes at OFF_RUMBLE / OFF_RUMBLE+1.
        let (large, small) = unsafe { (*base.add(OFF_RUMBLE), *base.add(OFF_RUMBLE + 1)) };
        Some((large, small))
    }
}

// The abandoned-rumble force-off window is shared with the UHID/UMDF pump —
// `uhid_manager::rumble_idle_timeout()` (`PUNKTFUNK_RUMBLE_IDLE_MS` hatch, default 2.5 s). XInput
// vibration is level-triggered — it persists until the game sets it to zero — so a game that
// latches a rumble and then stops calling `XInputSetState` (a residual left at a menu / loading
// screen, or a plain forgotten stop) would otherwise drone to the client forever (measured: a
// stuck `(0,512)` resent every 500 ms for 5.5 minutes). A real controller stops when the app
// stops driving it; the force-off mirrors that. Unlike the DualSense-family backends there are no
// flag semantics to key on — every `SET_STATE` IS a rumble write — so this path's any-activity
// keying is already rumble-keyed by construction. The shared window stays above SDL's ~2 s
// internal rumble resend so an SDL-driven host game (which re-issues the same level every ~2 s)
// refreshes the activity clock before it fires.
/// All virtual Xbox 360 pads of a session — the Windows analogue of the Linux uinput-xpad manager,
/// now backed by the XUSB companion driver. Same method surface (`new`/`handle`/`pump_rumble`) the
/// session input thread already drives.
pub struct GamepadManager {
    slots: PadSlots<XusbWinPad>,
    last_rumble: Vec<(u8, u8)>,
    /// When the game last drove each pad (bumped `rumble_seq` via `SET_STATE`). A non-zero
    /// `last_rumble` older than the shared idle window
    /// ([`crate::uhid_manager::rumble_idle_timeout`]) against this is a stale residual — see the
    /// comment above [`GamepadManager`].
    last_active: Vec<Instant>,
}

impl Default for GamepadManager {
    fn default() -> GamepadManager {
        GamepadManager::new()
    }
}

impl GamepadManager {
    pub fn new() -> GamepadManager {
        GamepadManager {
            slots: PadSlots::new(
                "Xbox 360/Windows",
                "Xbox 360",
                " (install/repair: punktfunk-host.exe driver install --gamepad)",
            ),
            last_rumble: vec![(0, 0); MAX_PADS],
            last_active: (0..MAX_PADS).map(|_| Instant::now()).collect(),
        }
    }

    /// How many virtual pads this manager has actually BUILT — the bring-up harness's
    /// "did the create happen?" check; see [`crate::uhid_manager::UhidManager::live_pads`] for why
    /// only a harness should ask.
    pub fn live_pads(&self) -> usize {
        self.slots.live()
    }

    fn ensure(&mut self, idx: usize) {
        if self.slots.ensure(idx, XusbWinPad::open) {
            tracing::info!(
                index = idx,
                "virtual Xbox 360 created (Windows XUSB companion)"
            );
            self.last_rumble[idx] = (0, 0);
            self.last_active[idx] = Instant::now();
        }
    }

    pub fn handle(&mut self, ev: &GamepadEvent) {
        match ev {
            GamepadEvent::Arrival { index, kind, .. } => {
                tracing::info!(index, kind, "controller arrival (Xbox 360/Windows)");
                self.ensure(*index as usize);
            }
            GamepadEvent::State(f) => {
                let idx = f.index as usize;
                if idx >= MAX_PADS {
                    return;
                }
                // Unplugs: arm the grace for any pad whose mask bit cleared (the drop itself lands
                // on a later `pump_rumble` tick — this frame is the only one the producer sends).
                // XUSB pads carry no rich plane, so a grace re-claim has nothing to clear.
                let swept = self.slots.sweep(f.active_mask).dropped;
                self.reset_swept(swept);
                if f.active_mask & (1 << idx) == 0 {
                    return;
                }
                self.ensure(idx);
                if let Some(pad) = self.slots.get_mut(idx) {
                    pad.write_state(
                        (f.buttons & 0xffff) as u16,
                        f.left_trigger,
                        f.right_trigger,
                        f.ls_x,
                        f.ls_y,
                        f.rs_x,
                        f.rs_y,
                    );
                }
            }
        }
    }

    /// Reset the sibling state of every index a sweep or reap just dropped, so both halves of the
    /// unplug clear the same things.
    fn reset_swept(&mut self, swept: u16) {
        for i in 0..MAX_PADS {
            if swept & (1 << i) != 0 {
                self.last_rumble[i] = (0, 0);
                self.last_active[i] = Instant::now();
            }
        }
    }

    /// Relay any changed rumble level to the client. XUSB motors are 0..255; the wire carries
    /// 0..65535, so scale by 257. `large` (low-frequency) → the datagram's `low`, `small`
    /// (high-frequency) → `high` — matching the other backends.
    ///
    /// The two trigger levels `send` also takes are always zero here and always will be: the XUSB
    /// `SET_STATE` packet this backend parses carries `rumble_large`/`rumble_small` and nothing
    /// else, mirroring `XINPUT_VIBRATION`'s two members. Impulse-trigger rumble is only reachable
    /// through the HID-visible Xbox identity (WGI / GameInput), never through the XUSB companion.
    pub fn pump_rumble(&mut self, mut send: impl FnMut(u16, u16, u16, u16, u16)) {
        // Finish any unplug whose removal frame only armed the grace — the producer sends that
        // frame once, so without this the XUSB devnode would outlive the controller.
        let swept = self.slots.reap();
        self.reset_swept(swept);
        for (i, pad) in self.slots.iter_mut() {
            if let Some((large, small)) = pad.service() {
                // The game drove the pad this poll (SET_STATE bumped the seq) — refresh the
                // activity clock even when the level is unchanged, so a rumble it keeps asserting
                // never trips the stale-residual timeout below.
                self.last_active[i] = Instant::now();
                if self.last_rumble[i] != (large, small) {
                    self.last_rumble[i] = (large, small);
                    send(i as u16, large as u16 * 257, small as u16 * 257, 0, 0);
                }
            } else if self.last_rumble[i] != (0, 0)
                && crate::uhid_manager::rumble_idle_timeout()
                    .is_some_and(|t| self.last_active[i].elapsed() >= t)
            {
                // A non-zero rumble is latched but the game has not driven the pad for the shared
                // idle window — a residual it forgot to stop. Force it off (and forward the zero)
                // so the resend loop stops droning it to the client. See the comment above
                // `GamepadManager`.
                tracing::info!(
                    index = i,
                    prev_low = self.last_rumble[i].0 as u16 * 257,
                    prev_high = self.last_rumble[i].1 as u16 * 257,
                    "rumble: stale residual (game stopped driving the pad) — forcing off"
                );
                self.last_rumble[i] = (0, 0);
                send(i as u16, 0, 0, 0, 0);
            }
        }
    }
}
