//! P5-46 / P6-1 — System power management (call continuity).
//!
//! Prevents the system from sleeping / turning off the display while a voice or
//! video call is active, then releases the request when the call ends. The web
//! client calls `set_call_active(true|false)` from `useTauriCallPower` as the
//! call-embed atom transitions.
//!
//! Windows: `SetThreadExecutionState`. The request is per-thread and persists
//! until cleared, so we run every set/clear on the **main thread** (via
//! `run_on_main_thread`) to guarantee the clear cancels the matching set even
//! though Tauri commands otherwise run on a pool thread.
//!
//! Linux (P6-1): `org.freedesktop.ScreenSaver` `Inhibit`/`UnInhibit` over the
//! session bus (zbus, blocking API). The inhibit cookie returned by `Inhibit`
//! is stored in Tauri managed state (`ScreenSaverInhibit`) so the later
//! `UnInhibit` can release exactly that request. The owning D-Bus **connection**
//! is stored alongside the cookie and kept alive for the inhibit's whole
//! duration: `org.freedesktop.ScreenSaver` auto-releases an inhibit the instant
//! the connection that took it disappears, so a per-call function-local
//! connection would drop the inhibit immediately. The one connection is opened
//! lazily on first inhibit and reused for the matching `UnInhibit`. All D-Bus
//! failures are logged and swallowed — a missing/absent screensaver service must
//! never break a call.
//!
//! macOS is out of scope for P6-1 (would use `IOPMAssertionCreate`) and stays a
//! no-op; the command stays cross-platform so the web side is unconditional.

use tauri::AppHandle;

/// The long-lived screensaver-inhibit state (Linux only). Both fields live
/// behind ONE mutex so the connection and the cookie it produced can never
/// desync: `conn` is the session-bus connection that *owns* the inhibit, and
/// `cookie` is the handle returned by `Inhibit`. The connection is opened once
/// (lazily, on the first inhibit) and reused for the matching `UnInhibit`;
/// keeping it alive here is what stops the screensaver service from
/// auto-releasing the inhibit.
#[cfg(target_os = "linux")]
#[derive(Default)]
struct InhibitState {
    conn: Option<zbus::blocking::Connection>,
    cookie: Option<u32>,
}

/// Tauri managed state wrapper. Registered in `setup()` and read by
/// `set_call_active` via `AppHandle::state()`.
#[cfg(target_os = "linux")]
pub struct ScreenSaverInhibit(std::sync::Mutex<InhibitState>);

/// Register the Linux screensaver-inhibit managed state. No-op elsewhere.
/// Called once from `native::setup()`.
pub fn setup(app: &AppHandle) -> tauri::Result<()> {
    #[cfg(target_os = "linux")]
    {
        use tauri::Manager;
        app.manage(ScreenSaverInhibit(std::sync::Mutex::new(
            InhibitState::default(),
        )));
    }
    #[cfg(not(target_os = "linux"))]
    let _ = app;
    Ok(())
}

#[tauri::command]
pub fn set_call_active(app: AppHandle, active: bool) {
    #[cfg(target_os = "windows")]
    {
        let _ = app.run_on_main_thread(move || {
            use windows::Win32::System::Power::{
                SetThreadExecutionState, ES_CONTINUOUS, ES_DISPLAY_REQUIRED, ES_SYSTEM_REQUIRED,
            };
            let flags = if active {
                ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
            } else {
                // Clearing to ES_CONTINUOUS alone releases the sleep/display
                // requirement while leaving no lingering per-thread state.
                ES_CONTINUOUS
            };
            // Safety: FFI call with no pointers; returns the previous state,
            // which we don't need.
            unsafe {
                SetThreadExecutionState(flags);
            }
        });
    }

    #[cfg(target_os = "linux")]
    {
        use tauri::Manager;

        // Serialize access to the stored connection+cookie for the duration of
        // the D-Bus round-trip. This command is the only touch point, so holding
        // the lock across the (short, blocking) call cannot deadlock.
        let state = app.state::<ScreenSaverInhibit>();
        let mut inner = match state.0.lock() {
            Ok(guard) => guard,
            Err(e) => {
                eprintln!("power: ScreenSaverInhibit mutex poisoned: {e}");
                return;
            }
        };

        if active {
            // Only take a new inhibit if one isn't already held, so repeated
            // set_call_active(true) calls don't leak cookies.
            if inner.cookie.is_none() {
                // Lazily open the ONE long-lived session connection. Because the
                // screensaver service auto-releases an inhibit when the owning
                // connection disappears, this connection must outlive the
                // inhibit — it stays in managed state and is reused below for
                // UnInhibit. Never reopened once established.
                if inner.conn.is_none() {
                    match zbus::blocking::Connection::session() {
                        Ok(conn) => inner.conn = Some(conn),
                        Err(e) => {
                            eprintln!("power: D-Bus session connection failed: {e}");
                            return;
                        }
                    }
                }
                // Scope the connection borrow so it ends before we mutate
                // `inner.cookie` (both go through the MutexGuard's Deref, which
                // borrows the whole guard, so the borrows must not overlap).
                let res: zbus::Result<u32> = {
                    let conn = inner
                        .conn
                        .as_ref()
                        .expect("connection set immediately above");
                    match zbus::blocking::Proxy::new(
                        conn,
                        "org.freedesktop.ScreenSaver",
                        "/org/freedesktop/ScreenSaver",
                        "org.freedesktop.ScreenSaver",
                    ) {
                        Ok(proxy) => proxy.call("Inhibit", &("Lotus Chat", "In a Lotus Chat call")),
                        Err(e) => {
                            eprintln!("power: ScreenSaver proxy init failed: {e}");
                            return;
                        }
                    }
                };
                match res {
                    Ok(cookie) => inner.cookie = Some(cookie),
                    Err(e) => eprintln!("power: ScreenSaver Inhibit failed: {e}"),
                }
            }
        } else if let Some(cookie) = inner.cookie.take() {
            // Release on the SAME connection that took the inhibit. If it's
            // somehow gone the inhibit was already auto-released, so nothing to do.
            if let Some(conn) = inner.conn.as_ref() {
                match zbus::blocking::Proxy::new(
                    conn,
                    "org.freedesktop.ScreenSaver",
                    "/org/freedesktop/ScreenSaver",
                    "org.freedesktop.ScreenSaver",
                ) {
                    Ok(proxy) => {
                        let res: zbus::Result<()> = proxy.call("UnInhibit", &(cookie,));
                        if let Err(e) = res {
                            eprintln!("power: ScreenSaver UnInhibit failed: {e}");
                        }
                    }
                    Err(e) => eprintln!("power: ScreenSaver proxy init failed: {e}"),
                }
            }
        }
    }

    #[cfg(not(any(target_os = "windows", target_os = "linux")))]
    {
        // No-op on other platforms (see module docs). Bind the args so the
        // signature stays identical cross-platform and no unused warnings fire.
        let _ = (&app, active);
    }
}
