pragma Singleton

// What this machine offers to other machines: remote login, remote desktop,
// and the two services that would provide file and media sharing if they were
// installed.
//
// A service that is absent is reported as absent rather than shown as a switch
// that would do nothing -- which is the failure mode of the panel this replaces.
//
// Turning remote login on is a system-wide change and goes through pkexec, so
// it prompts. Remote desktop is a user service and does not.

import Quickshell
import Quickshell.Io
import QtQuick

Singleton {
    id: root

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

    property string hostname: ""
    property string prettyHostname: ""
    property var remoteLogin: ({})
    property var remoteDesktop: ({})
    property var fileSharing: ({})
    property var mediaSharing: ({})
    property bool scanned: false
    property string lastError: ""

    // Guards read the Process objects directly; a derived binding is stale
    // inside the handler that changes it. See DefaultApps.qml.
    readonly property bool busy: query.running || mutation.running

    // The name someone types to reach this machine.
    readonly property string networkName: root.hostname !== "" ? root.hostname : "this machine"

    readonly property bool remoteLoginOn: root.remoteLogin?.active === true

    // People signed in from another machine right now. Empty is the normal
    // case; the list exists so that "someone is on this machine" is something
    // the page can state rather than something you have to go and check.
    readonly property var remoteSessions: Array.isArray(root.remoteLogin?.sessions)
        ? root.remoteLogin.sessions
        : []
    readonly property bool remoteDesktopOn: root.remoteDesktop?.active === true

    // sshd's configuration only sometimes states this. Saying "keys only" when
    // the file is silent would be a security claim that cannot be backed up.
    function passwordLoginSummary(): string {
        const stated = String(root.remoteLogin?.passwordAuthentication ?? "");
        if (stated === "")
            return "Not configured, so the system default applies";
        return stated.toLowerCase() === "no"
            ? "Refused; keys only"
            : "Allowed";
    }

    function refresh(): void {
        if (query.running)
            return;
        query.command = [root.helperPath, "snapshot"];
        query.running = true;
    }

    function absorb(text: string): void {
        try {
            const parsed = JSON.parse(text);
            root.hostname = String(parsed.hostname ?? "");
            root.prettyHostname = String(parsed.prettyHostname ?? "");
            root.remoteLogin = parsed.remoteLogin ?? ({});
            root.remoteDesktop = parsed.remoteDesktop ?? ({});
            root.fileSharing = parsed.fileSharing ?? ({});
            root.mediaSharing = parsed.mediaSharing ?? ({});
            root.lastError = String(parsed.error ?? "");
        } catch (error) {
            root.lastError = "Could not read the sharing helper's answer.";
            console.warn("Sharing: could not parse helper output:", error);
        }
        root.scanned = true;
    }

    function run(arguments: var): void {
        if (mutation.running)
            return;
        root.lastError = "";
        mutation.command = [root.helperPath].concat(arguments);
        mutation.running = true;
    }

    function setRemoteLogin(enabled: bool): void {
        root.run(["set-remote-login", enabled ? "true" : "false"]);
    }

    function setRemoteDesktop(enabled: bool): void {
        root.run(["set-remote-desktop", enabled ? "true" : "false"]);
    }

    function setHostname(name: string): void {
        root.run(["set-hostname", name]);
    }

    function setRdpPort(port: string): void {
        root.run(["set-rdp-port", port]);
    }

    // Rygel serves media to devices over DLNA. Turning it on publishes media
    // directories to every device on the network, so the page says that next to
    // the switch rather than after the fact.
    function setMediaSharing(enabled: bool): void {
        root.run(["set-media-sharing", enabled ? "true" : "false"]);
    }

    function setRdpViewOnly(viewOnly: bool): void {
        root.run(["set-rdp-view-only", viewOnly ? "true" : "false"]);
    }

    function clearRdpCredentials(): void {
        root.run(["clear-rdp-credentials"]);
    }

    // Setting credentials opens a terminal running gnome-remote-desktop's own
    // tool, which prompts for the password itself.
    //
    // That is not a cop-out, it is the only safe path: grdctl takes the
    // password on a terminal and CRASHES without one, and the alternative --
    // passing it as an argument -- would publish it through /proc to every
    // process on this machine. Typed into grdctl directly, it never passes
    // through Panama at all.
    function setRdpCredentials(userName: string): void {
        Quickshell.execDetached(["kitty", "--hold", "-e",
                                 "grdctl", "rdp", "set-credentials", userName]);
    }

    Process {
        id: query
        stdout: StdioCollector { onStreamFinished: root.absorb(this.text) }
        stderr: StdioCollector {
            onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim()
        }
    }

    Process {
        id: mutation
        // Answers with the fresh state, so the page updates from the change
        // itself rather than asking again afterwards.
        stdout: StdioCollector { onStreamFinished: root.absorb(this.text) }
        stderr: StdioCollector {
            onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim()
        }
    }
}
