//! Session lifecycle on `Forge`: display power (DPMS), output reprobe, session
//! lock, orientation lock, idle-activity and night-light gamma. Operates on
//! `Forge`.

use super::*;

impl Forge {
    pub fn set_display_status(&mut self, on: bool) {
        // Waking is requested from two places at once — the key that
        // ends the blank, and the idle daemon's resume action. Applying
        // it twice runs two modesets back to back and the flips that
        // follow fail with EBUSY.
        if on == self.session.display_on && self.backend.applied_display_status() == Some(on) {
            info!("set_display_status: already on={on}, ignoring");
            return;
        }
        info!("set_display_status: on={on} (was {})", self.session.display_on);
        self.session.display_on = on;

        if on {
            self.protocol.idle_notifier_state.notify_activity(&self.seat);
            if !self.windows.orientation_locked() {
                trace_error!(self.event_loop.enable(&self.accel_token));
            }
        } else {
            trace_error!(self.event_loop.disable(&self.accel_token));
        }

        let soft = self.windows.canvas().is_mobile();
        self.backend.set_display_status(on, soft);
        self.report_session_state();
    }

    /// Deduplicated, so cheap to call from the frame path.
    pub fn report_session_state(&self) {
        crate::powermanager_dbus::set_form_factor(self.windows.canvas().is_mobile());
        crate::powermanager_dbus::set_session_state(self.session.display_on, self.windows.is_locked());
    }

    /// Re-probe DRM connectors. Hotplug events that happen while the
    /// machine is suspended never reach us, so the output set has to be
    /// rebuilt from the hardware on resume rather than assumed.
    pub fn reprobe_outputs(&mut self) {
        if matches!(self.backend, crate::backend::Backend::Udev(_)) {
            crate::udev::reprobe_all(self);
        }
    }

    /// Lock the session by launching the `lockscreen --lock` client, which
    /// drives `ext-session-lock-v1` (forge implements the compositor side via
    /// `SessionLockHandler`). No-op while a lock request is pending or a live
    /// client already holds the lock, so repeated triggers (lid, idle, power,
    /// display wake) don't stack lockers and corrupt the lock. A lock whose
    /// client died is not counted, so a crashed locker gets respawned.
    /// Kiosk sessions never lock — there is nothing to protect and no
    /// credentials to unlock with.
    pub fn lock_session(&self) {
        if self.session.greeter_mode
            || self.session.kiosk
            || self.session.locker.is_some()
            || self.session.active_lock.as_ref().is_some_and(|l| l.is_alive())
        {
            return;
        }
        let bin =
            std::env::var("LOCKSCREEN_BIN").unwrap_or_else(|_| "/usr/bin/lockscreen".to_string());
        let mut cmd = std::process::Command::new(&bin);
        cmd.arg("--lock").stdin(std::process::Stdio::null());
        crate::spawn::detached(cmd);
    }

    pub fn lock_orientation(&mut self, orientation: Option<Orientation>) {
        trace_error!(self.event_loop.disable(&self.accel_token));
        self.windows.lock_orientation(orientation);
        self.force_redraw(true);
    }

    pub fn unlock_orientation(&mut self) {
        trace_error!(self.event_loop.enable(&self.accel_token));
        self.windows.unlock_orientation();
        self.force_redraw(true);
    }

    /// Capture a window thumbnail as RGBA file for the shell's overview.
    /// Remove thumbnail files left in the runtime dir by a previous session.

    /// Reset the idle-activity timer (called from the input path on any event).
    pub fn notify_idle_activity(&mut self) {
        self.protocol.idle_notifier_state.notify_activity(&self.seat);
    }

    /// The dmabuf global's two collaborators — the protocol state and the
    /// display handle — as disjoint borrows, so a backend can publish the
    /// global without reaching into the private protocol state.

    /// Apply night light gamma ramp based on color temperature (Kelvin).
    pub fn apply_night_light_gamma(&mut self, temperature: u32) {
        // Convert color temperature to RGB multipliers (Tanner Helland algorithm)
        let temp = temperature.clamp(1000, 10000) as f64 / 100.0;

        let r = if temp <= 66.0 {
            1.0
        } else {
            let r = 329.698727446 * (temp - 60.0).powf(-0.1332047592);
            (r / 255.0).clamp(0.0, 1.0)
        };

        let g = if temp <= 66.0 {
            let g = 99.4708025861 * temp.ln() - 161.1195681661;
            (g / 255.0).clamp(0.0, 1.0)
        } else {
            let g = 288.1221695283 * (temp - 60.0).powf(-0.0755148492);
            (g / 255.0).clamp(0.0, 1.0)
        };

        let b = if temp >= 66.0 {
            1.0
        } else if temp <= 19.0 {
            0.0
        } else {
            let b = 138.5177312231 * (temp - 10.0).ln() - 305.0447927307;
            (b / 255.0).clamp(0.0, 1.0)
        };

        // Apply via brightnessctl or direct DRM gamma if available
        // For now, use wl-gammarelay or gammastep-compatible approach
        let temp_arg = temperature.to_string();
        std::thread::spawn(move || {
            let _ = std::process::Command::new("busctl")
                .args([
                    "--user",
                    "call",
                    "rs.wl-gammarelay",
                    "/",
                    "rs.wl.gammarelay",
                    "UpdateTemperature",
                    "q",
                    &temp_arg,
                ])
                .stdin(std::process::Stdio::null())
                .stdout(std::process::Stdio::null())
                .stderr(std::process::Stdio::null())
                .status();
        });

        info!("Night light: {temperature}K (R={r:.2} G={g:.2} B={b:.2})");
    }

    /// Reset gamma to default (disable night light).
    pub fn reset_gamma(&mut self) {
        std::thread::spawn(|| {
            let _ = std::process::Command::new("busctl")
                .args([
                    "--user",
                    "call",
                    "rs.wl-gammarelay",
                    "/",
                    "rs.wl.gammarelay",
                    "UpdateTemperature",
                    "q",
                    "6500",
                ])
                .stdin(std::process::Stdio::null())
                .stdout(std::process::Stdio::null())
                .stderr(std::process::Stdio::null())
                .status();
        });

        info!("Night light disabled (gamma reset to 6500K)");
    }

}
