//! P5-49 — Network awareness (Windows connectivity / NCSI).
//!
//! Proactively detects when the machine gains or loses internet connectivity so
//! the web client can surface an offline state and, more importantly, nudge the
//! matrix client to retry its backed-off `/sync` the instant the network comes
//! back instead of waiting out the sync-loop backoff timer.
//!
//! Windows: a lightweight background thread polls the Network List Manager
//! (`INetworkListManager::IsConnectedToInternet`, the same NCSI signal the shell
//! uses) every ~3 seconds. We prefer a robust poll over a COM event sink
//! (`INetworkEvents`) — the poll is far simpler to reason about, needs no
//! connection-point plumbing, and a 3s cadence is more than responsive enough
//! for a "retry sync now" hint. We emit **only on a state transition**, so the
//! web side gets one event per change rather than a steady heartbeat.
//!
//! Other platforms are a no-op: the browser already fires `online`/`offline`
//! events, and the desktop shells (macOS/Linux) can adopt their own reachability
//! APIs later; the web hook stays unconditional so nothing there needs guarding.

use tauri::AppHandle;

/// Payload for the `network-changed` DOM event (`{ online: bool }`).
#[cfg(target_os = "windows")]
#[derive(serde::Serialize)]
struct NetworkState {
    online: 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_network(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_network(app: AppHandle) {
    use std::time::Duration;
    use windows::Win32::Networking::NetworkListManager::{INetworkListManager, NetworkListManager};
    use windows::Win32::System::Com::{
        CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_INPROC_SERVER,
        COINIT_MULTITHREADED,
    };

    // 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;
    }

    // Create the Network List Manager COM object (mirrors the `CoCreateInstance`
    // idiom in lib.rs `set_badge_count`). If it's unavailable, tear COM back down
    // and stop the thread cleanly.
    // Safety: standard COM instantiation; type is inferred from the annotation.
    let manager: INetworkListManager =
        match unsafe { CoCreateInstance(&NetworkListManager, None, CLSCTX_INPROC_SERVER) } {
            Ok(manager) => manager,
            Err(_) => {
                // Safety: balances the successful CoInitializeEx above.
                unsafe { CoUninitialize() };
                return;
            }
        };

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

    loop {
        // `IsConnectedToInternet` yields a VARIANT_BOOL (VARIANT_TRUE == -1 when
        // connected). Skip transient read errors without emitting.
        // Safety: FFI call on a live COM interface owned by this thread.
        if let Ok(connected) = unsafe { manager.IsConnectedToInternet() } {
            let online = connected.as_bool();
            if last != Some(online) {
                last = Some(online);
                super::emit_to_web(
                    &app,
                    "network-changed",
                    &serde_json::to_string(&NetworkState { online }).unwrap_or_default(),
                );
            }
        }

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

    // 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.
}
