pragma Singleton

// Idle, lock, and sleep timings.
//
// hypridle has no IPC for reconfiguration, and its config is hyprlang rather
// than the shared JSON, so this cannot work the way the compositor settings do.
// Instead scripts/panama-idle regenerates a config from the settings store and
// restarts the daemon.
//
// The generated file lives under XDG_STATE_HOME rather than ~/.config/hypr,
// because that directory is a symlink into the Panama repository -- writing
// there at runtime would put machine state into a tracked file. A systemd
// drop-in points hypridle at the generated path with `-c`.
//
// "Managed" is therefore a real state with two sides: when the drop-in is
// installed the timings below are in effect, and when it is not, hypridle is
// running the repository's shipped hypridle.conf and these values are only a
// stored intention. The Power page says which it is rather than showing
// controls that quietly do nothing.

import Quickshell
import Quickshell.Io
import QtQuick
import qs.config

Singleton {
    id: root

    readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-idle"

    property bool managed: false
    property string serviceState: "unknown"
    property string generatedPath: ""
    property string lastError: ""

    readonly property bool busy: statusQuery.running || applyRun.running

    // The values as stored. They only describe what is running when `managed`.
    readonly property int blankMinutes: DesktopPreferences.get("screenBlankMinutes")
    readonly property int lockMinutes: DesktopPreferences.get("lockMinutes")
    readonly property int suspendMinutes: DesktopPreferences.get("suspendMinutes")
    readonly property bool lockOnSleep: DesktopPreferences.get("lockOnSleep")

    // Blanking after locking is legal but pointless, and blanking with lock off
    // is fine. Surfacing the one genuinely confusing combination beats silently
    // reordering the user's numbers.
    readonly property bool lockBeforeBlank: root.lockMinutes > 0
        && root.blankMinutes > 0
        && root.lockMinutes < root.blankMinutes

    Process {
        id: statusQuery
        command: [root.helperPath, "status"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const state = JSON.parse(this.text);
                    root.managed = state.managed === true;
                    root.serviceState = String(state.active ?? "unknown");
                    root.generatedPath = String(state.generated ?? "");
                    root.lastError = "";
                } catch (error) {
                    root.lastError = "Could not read the idle configuration.";
                }
            }
        }
    }

    Process {
        id: applyRun
        onExited: (exitCode, exitStatus) => {
            root.lastError = exitCode === 0 ? "" : "Could not update the idle configuration.";
            root.refresh();
        }
    }

    Component.onCompleted: root.refresh()

    function refresh(): void {
        if (!statusQuery.running)
            statusQuery.running = true;
    }

    // Regenerates the config from the current settings and restarts hypridle if
    // Panama is managing it. Safe to call when it is not: the file is written
    // and nothing is restarted.
    function apply(): void {
        if (applyRun.running)
            return;
        applyRun.exec([root.helperPath, "apply"]);
    }

    function setManaged(enabled: bool): void {
        if (applyRun.running)
            return;
        applyRun.exec([root.helperPath, enabled ? "install" : "remove"]);
    }

    // Regenerate whenever one of the four inputs changes. Coalesced, because a
    // slider drag settles through several commits and each one would otherwise
    // restart the daemon.
    Connections {
        target: DesktopPreferences
        function onRevisionChanged(): void {
            if (root.managed)
                regenerate.restart();
        }
    }

    // The charger came or went. hypridle cannot express two sets of timeouts,
    // so the config is rebuilt from the other set and the daemon restarted --
    // the same path a settings change takes, through the same debounce, so a
    // flapping charger cannot restart hypridle in a loop.
    Connections {
        target: Battery
        function onAcChanged(online: bool): void {
            if (root.managed)
                regenerate.restart();
        }
    }

    Timer {
        id: regenerate
        interval: 400
        onTriggered: root.apply()
    }
}
