pragma Singleton

// SSH keys, the agent holding them, and the hosts this machine has met.
//
// Nothing here ever sees a private key or a passphrase. Adding an encrypted key
// makes ssh-add prompt through the system's own askpass, which is where a
// passphrase belongs -- a settings page collecting one and passing it along
// would be a worse place for it to live.

import Quickshell
import Quickshell.Io
import QtQuick

Singleton {
    id: root

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

    property bool available: false
    property string directory: ""
    property var agent: ({})
    property var keys: []
    property var hosts: []
    property bool scanned: false
    property string lastError: ""

    readonly property bool busy: query.running || mutation.running

    readonly property int loadedCount: root.keys.filter(key => key.loaded === true).length

    // Keys readable by anyone but their owner. ssh refuses to use these, so a
    // page that stayed quiet about it would leave someone wondering why a key
    // that plainly exists is never offered.
    readonly property var overexposed: root.keys.filter(key =>
        key.mode !== "" && key.mode !== "600" && key.mode !== "400")

    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.available = parsed.available === true;
            root.directory = String(parsed.directory ?? "");
            root.agent = parsed.agent ?? ({});
            root.keys = Array.isArray(parsed.keys) ? parsed.keys : [];
            root.hosts = Array.isArray(parsed.hosts) ? parsed.hosts : [];
            root.lastError = String(parsed.error ?? "");
        } catch (error) {
            root.lastError = "Could not read the SSH configuration.";
            console.warn("SshKeys: 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;
    }

    // Adding an encrypted key prompts, and the prompt is the system's, so this
    // is allowed a long time before it is considered stuck.
    function addToAgent(path: string): void { root.run(["agent-add", path]); }

    // The public half, onto the clipboard. Safe to copy by definition -- it is
    // the thing you paste into a server. The path arrives as $1 rather than
    // being spliced into shell source, so a name with a space or a quote in it
    // cannot become part of the command.
    function copyPublicKey(publicPath: string): void {
        if (publicPath === "" || copier.running)
            return;
        copier.command = ["sh", "-c", 'exec wl-copy < "$1"', "qs-ssh-keys", publicPath];
        copier.running = true;
    }
    function forgetHost(host: string): void { root.run(["forget-host", host]); }

    Component.onCompleted: root.refresh()

    Process { id: copier }

    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
        stdout: StdioCollector { onStreamFinished: root.absorb(this.text) }
        stderr: StdioCollector {
            onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim()
        }
    }
}
