//! Windows system sleep/wake via `PowerRegisterSuspendResumeNotification`
//! with a `DEVICE_NOTIFY_CALLBACK` recipient — no hidden window or message
//! loop required (Windows 8+).
//!
//! NOTE: this module only compiles when targeting Windows.

use std::os::raw::c_void;

use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Power::{
    DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS, HPOWERNOTIFY, PowerRegisterSuspendResumeNotification,
    PowerUnregisterSuspendResumeNotification,
};
use windows_sys::Win32::UI::WindowsAndMessaging::DEVICE_NOTIFY_CALLBACK;

use super::{PowerCallback, PowerEvent};

// Power-broadcast event types (WM_POWERBROADCAST `wParam`).
const PBT_APMSUSPEND: u32 = 0x0004;
const PBT_APMRESUMESUSPEND: u32 = 0x0007;
const PBT_APMRESUMEAUTOMATIC: u32 = 0x0012;

const ERROR_SUCCESS: u32 = 0;

/// Heap-pinned so its address stays stable for the registration lifetime; the
/// raw pointer is handed to the OS as the callback context.
struct Context {
    callback: PowerCallback,
}

pub(crate) struct Listener {
    // Registration handle from `PowerRegisterSuspendResumeNotification`
    // (a `*mut c_void`; cast to `HPOWERNOTIFY` for unregister).
    handle: *mut c_void,
    // Kept alive (and freed in `Drop`) because the OS holds a raw pointer to it.
    ctx: *mut Context,
}

// The OS invokes the callback on an arbitrary thread; the handle is only used
// to unregister. `PowerCallback` is `Send + Sync`.
unsafe impl Send for Listener {}
unsafe impl Sync for Listener {}

impl Listener {
    pub(crate) fn start(callback: PowerCallback) -> Option<Self> {
        let ctx = Box::into_raw(Box::new(Context { callback }));

        let mut params = DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS {
            Callback: Some(power_callback),
            Context: ctx as *mut c_void,
        };

        let mut handle: *mut c_void = std::ptr::null_mut();
        let status = unsafe {
            PowerRegisterSuspendResumeNotification(
                DEVICE_NOTIFY_CALLBACK,
                &mut params as *mut DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS as HANDLE,
                &mut handle,
            )
        };

        if status != ERROR_SUCCESS || handle.is_null() {
            unsafe { drop(Box::from_raw(ctx)) };
            return None;
        }

        Some(Self { handle, ctx })
    }
}

impl Drop for Listener {
    fn drop(&mut self) {
        unsafe {
            PowerUnregisterSuspendResumeNotification(self.handle as HPOWERNOTIFY);
            drop(Box::from_raw(self.ctx));
        }
    }
}

unsafe extern "system" fn power_callback(
    context: *const c_void,
    event_type: u32,
    _setting: *const c_void,
) -> u32 {
    // Safe: `context` is the live `Context` we registered with.
    let ctx = unsafe { &*(context as *const Context) };
    match event_type {
        PBT_APMSUSPEND => (ctx.callback)(PowerEvent::WillSleep),
        // A single resume can deliver both PBT_APMRESUMEAUTOMATIC and
        // PBT_APMRESUMESUSPEND, so `DidWake` may fire twice per wake. That is
        // fine and intentional: lowering the sleep gate is idempotent, so a
        // duplicate wake is harmless — do not try to "dedupe" this later.
        PBT_APMRESUMEAUTOMATIC | PBT_APMRESUMESUSPEND => (ctx.callback)(PowerEvent::DidWake),
        _ => {}
    }
    ERROR_SUCCESS
}

pub(crate) fn current_power_state() -> crate::PowerState {
    // No synchronous dark-wake query wired up on Windows; report Unknown so
    // callers fall back to the suspend/resume notification path.
    crate::PowerState::Unknown
}
