pragma Singleton

// System date, time, and timezone.
//
// Deliberately NOT backed by the Panama settings store. The timezone and the
// network-time setting belong to the machine, not to this desktop: they are
// shared with every other session and with services that never see Panama's
// JSON. Storing a copy would create a second answer to a question the system
// already answers, which is the exact failure this settings rewrite exists to
// remove. So this reads and writes `timedatectl` directly and holds no state of
// its own beyond what it last observed.
//
// Setting the timezone or toggling NTP needs privilege. timedatectl asks
// polkit, which shows the usual authentication dialog; on refusal the command
// fails and the error is surfaced rather than the UI pretending it worked.

import Quickshell
import Quickshell.Io
import QtQuick

Singleton {
    id: root

    property string timezone: ""
    property bool ntpEnabled: false
    property bool ntpSynchronized: false
    property string localTime: ""
    property string universalTime: ""
    property string rtcTime: ""
    property string lastError: ""

    property var zones: []

    readonly property bool busy: statusQuery.running || zonesQuery.running || writeRun.running

    // "America/New_York" -> "New York" for display, keeping the region as a
    // separate field so the list can be grouped and searched sensibly.
    function regionOf(zone: string): string {
        const slash = zone.indexOf("/");
        return slash < 0 ? zone : zone.slice(0, slash);
    }

    function cityOf(zone: string): string {
        const slash = zone.indexOf("/");
        return (slash < 0 ? zone : zone.slice(slash + 1)).replace(/_/g, " ");
    }

    Process {
        id: statusQuery
        command: ["timedatectl", "show",
            "-p", "Timezone", "-p", "NTP", "-p", "NTPSynchronized",
            "-p", "TimeUSec", "-p", "RTCTimeUSec"]
        stdout: StdioCollector {
            onStreamFinished: root.parseStatus(this.text)
        }
        onExited: (exitCode, exitStatus) => {
            if (exitCode !== 0)
                root.lastError = "Could not read the system clock settings.";
        }
    }

    Process {
        id: zonesQuery
        command: ["timedatectl", "list-timezones"]
        stdout: StdioCollector {
            onStreamFinished: {
                root.zones = this.text.split("\n")
                    .map(line => line.trim())
                    .filter(line => line.length > 0);
            }
        }
    }

    Process {
        id: writeRun
        onExited: (exitCode, exitStatus) => {
            // A polkit refusal and a bad value both land here. Neither should
            // leave the UI showing a value the system did not take, so the
            // status is re-read either way.
            root.lastError = exitCode === 0
                ? ""
                : "The system rejected that change, or authentication was canceled.";
            root.refresh();
        }
    }

    Component.onCompleted: {
        root.refresh();
        zonesQuery.running = true;
    }

    function parseStatus(text: string): void {
        for (const line of text.split("\n")) {
            const split = line.indexOf("=");
            if (split < 0)
                continue;
            const key = line.slice(0, split);
            const value = line.slice(split + 1);
            if (key === "Timezone")
                root.timezone = value;
            else if (key === "NTP")
                root.ntpEnabled = value === "yes";
            else if (key === "NTPSynchronized")
                root.ntpSynchronized = value === "yes";
        }
        root.lastError = "";
    }

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

    // Only a timezone the system itself listed is ever passed on, so no
    // caller-supplied text reaches the command.
    function setTimezone(zone: string): bool {
        if (root.zones.indexOf(zone) < 0) {
            root.lastError = "That is not a timezone this system recognizes.";
            return false;
        }
        if (writeRun.running)
            return false;
        writeRun.exec(["timedatectl", "set-timezone", zone]);
        return true;
    }

    function setNtp(enabled: bool): bool {
        if (writeRun.running)
            return false;
        writeRun.exec(["timedatectl", "set-ntp", enabled ? "true" : "false"]);
        return true;
    }
}
