//! P5-56 — Windows Focus Assist ↔ Do-Not-Disturb sync.
//!
//! Mirrors the shell's own notification-suppression state so Lotus Chat stops
//! popping desktop notifications while the user is in Focus Assist / Quiet Hours,
//! presenting, gaming full-screen, or otherwise "busy". The web client keeps this
//! in a live jotai atom (`focusAssistActiveAtom`) that the notification gate reads
//! alongside its existing quiet-hours check.
//!
//! Windows: a lightweight background thread polls `SHQueryUserNotificationState`
//! (the same API the shell exposes for "should I show a toast right now?") every
//! ~5 seconds. We prefer a robust poll over hooking shell events — the poll is
//! trivial to reason about and a 5s cadence is more than responsive enough for a
//! notification-suppression hint. We emit **only on a boolean transition**, so the
//! web side gets one event per change rather than a steady heartbeat; the first
//! read always emits so the frontend learns the initial state.
//!
//! Other platforms are a no-op: there's no equivalent cross-platform signal, and
//! the web hook stays unconditional so nothing there needs guarding.

use tauri::AppHandle;

/// Payload for the `focus-assist-changed` DOM event (`{ active: bool }`).
#[cfg(target_os = "windows")]
#[derive(serde::Serialize)]
struct St {
    active: bool,
}

/// Called once from lib.rs `native::setup()`. On Windows, spawns the poll
/// thread; elsewhere it does nothing.
pub fn setup(app: &AppHandle) -> tauri::Result<()> {
    #[cfg(target_os = "windows")]
    {
        // Own a handle inside the thread so the poll outlives this call and runs
        // for the lifetime of the app.
        let app = app.clone();
        std::thread::spawn(move || watch_focus_assist(app));
    }

    #[cfg(not(target_os = "windows"))]
    {
        // No-op on non-Windows platforms (see module docs). Bind the arg so the
        // signature stays identical cross-platform with no unused warning.
        let _ = app;
    }

    Ok(())
}

/// Poll loop, runs on its own thread for the app's lifetime.
#[cfg(target_os = "windows")]
fn watch_focus_assist(app: AppHandle) {
    use std::time::Duration;
    use windows::Win32::System::Com::{CoInitializeEx, COINIT_MULTITHREADED};
    use windows::Win32::UI::Shell::{
        SHQueryUserNotificationState, QUNS_BUSY, QUNS_PRESENTATION_MODE, QUNS_QUIET_TIME,
        QUNS_RUNNING_D3D_FULL_SCREEN,
    };

    // Initialize COM for this thread in the multithreaded apartment. This is a
    // dedicated thread, so it should be the first to init and succeed (S_FALSE —
    // "already initialized, same mode" — also counts as success). If it fails
    // outright (e.g. RPC_E_CHANGED_MODE) we can't proceed, so bail.
    // Safety: FFI call; `None` reserved param per the API contract.
    if unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_err() {
        return;
    }

    // `None` = unknown; the first successful read is treated as a transition so
    // the web side always learns the initial suppression state.
    let mut last: Option<bool> = None;

    loop {
        // `SHQueryUserNotificationState` reports the shell's current
        // notification-presentation state. Treat the states where the shell
        // itself suppresses toasts as "focus/DND active". Skip transient read
        // errors without emitting.
        // Safety: FFI call; writes the state into the provided out-param.
        if let Ok(state) = unsafe { SHQueryUserNotificationState() } {
            let active = state == QUNS_QUIET_TIME
                || state == QUNS_PRESENTATION_MODE
                || state == QUNS_RUNNING_D3D_FULL_SCREEN
                || state == QUNS_BUSY;
            if last != Some(active) {
                last = Some(active);
                super::emit_to_web(
                    &app,
                    "focus-assist-changed",
                    &serde_json::to_string(&St { active }).unwrap_or_default(),
                );
            }
        }

        std::thread::sleep(Duration::from_secs(5));
    }

    // Note: the loop never returns, so we intentionally don't call
    // `CoUninitialize` here — COM stays initialized for this thread until the
    // process exits, which is exactly the desired lifetime.
}
