pragma Singleton

// ─────────────────────────────────────────────────────────────────────────────
// Whether the other screens join in on workspaces.
//
// The preference is ordinary; applying it is not. Workspace rules are read by
// Hyprland at config time and cannot be taken back at runtime -- writing a rule
// with an empty monitor leaves the old binding in place, which was checked
// rather than assumed. Only `hyprctl reload` clears them, and it re-runs the
// whole config, so monitors.lua re-emits exactly the set the preference asks
// for.
//
// That makes this service two things: the reload, and an honest answer to "has
// it actually taken effect yet". The second matters more. A page that offers a
// switch and silently does nothing until the next login is the failure this
// repository keeps refusing to ship, so `applied` is read back from the
// compositor rather than inferred from the preference having been written.
// ─────────────────────────────────────────────────────────────────────────────

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

Singleton {
    id: root

    // The stored intent.
    readonly property bool primaryOnly: DesktopPreferences.get("workspacesOnPrimaryOnly") === true

    // What the compositor is actually running, as reported by hyprctl.
    property var rules: []
    property bool reloading: false
    property string lastError: ""

    // The keybinds reach ALT+1..ALT+0, and monitors.lua pins that many.
    readonly property int boundWorkspaces: 10

    // Every monitor named by a rule. When workspaces are pinned there is
    // exactly one, and it is the primary.
    readonly property var pinnedMonitors: {
        const names = [];
        for (const rule of root.rules) {
            const monitor = rule?.monitor;
            if (typeof monitor === "string" && monitor !== "" && !names.includes(monitor))
                names.push(monitor);
        }
        return names;
    }

    // Has the compositor caught up with the preference?
    //
    // Pinned means every bound workspace carries a rule naming one monitor;
    // unpinned means no rules at all. Anything in between is a config that was
    // changed without a reload, which is precisely the state worth reporting.
    readonly property bool applied: root.primaryOnly
        ? (root.rules.length >= root.boundWorkspaces && root.pinnedMonitors.length === 1)
        : root.rules.length === 0

    Process {
        id: query
        command: ["hyprctl", "-j", "workspacerules"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const parsed = JSON.parse(this.text);
                    root.rules = Array.isArray(parsed) ? parsed : [];
                } catch (error) {
                    // An unparseable answer is not an empty rule set. Claiming
                    // it was would report "not pinned" for a desktop that is.
                    root.lastError = "Could not read the compositor's workspace rules.";
                }
            }
        }
    }

    Process {
        id: reloadRun
        command: ["hyprctl", "reload"]
        onExited: (exitCode, exitStatus) => {
            root.reloading = false;
            if (exitCode !== 0) {
                root.lastError = "The compositor did not reload.";
                return;
            }
            root.lastError = "";
            // The preference file is written on a timer and the reload has to
            // re-read it, so the rules are only worth re-reading once both have
            // had a moment.
            settle.restart();
        }
    }

    Timer {
        id: settle
        interval: 350
        onTriggered: root.refresh()
    }

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

    // Write the choice, then make it true. Split from the write on purpose: the
    // page confirms between the two, because a reload is felt across the whole
    // session rather than in one card.
    function choose(primaryOnly: bool): void {
        DesktopPreferences.set("workspacesOnPrimaryOnly", primaryOnly === true);
    }

    function apply(): void {
        if (reloadRun.running)
            return;
        root.reloading = true;
        reloadRun.running = true;
    }

    Component.onCompleted: root.refresh()
}
