pragma Singleton

// The fingerprint reader, for the Users page.
//
// Two facts, owned by two different systems: fprintd holds the enrolled
// prints (GNOME's Users panel owns the enrollment dialog and Panama hands off
// to it), and authselect decides whether PAM asks the reader at unlock. Both
// come through scripts/panama-fingerprint, and the one privileged change --
// flipping authselect's with-fingerprint feature -- prompts through polkit
// with a stated reason, like everything else on that page.
//
// Read when the page opens rather than at shell startup: probing fprintd
// bus-activates the daemon, and most sessions never open this page.

import Quickshell
import Quickshell.Io
import QtQuick

Singleton {
    id: root

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

    property bool readerPresent: false
    property string readerName: ""
    property var enrolled: []
    property bool pamEnabled: false
    property bool scanned: false
    property bool busy: false
    property string lastError: ""

    // "right-index-finger" -> "Right index finger". Presentation lives here
    // rather than in the page, the way PowerProfiles.label does, so nothing
    // can disagree about what a finger is called.
    function fingerLabel(finger: string): string {
        const words = String(finger).split("-").join(" ");
        return words.slice(0, 1).toUpperCase() + words.slice(1);
    }

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

    function setUnlockEnabled(on: bool): void {
        if (root.busy)
            return;
        root.busy = true;
        root.lastError = "";
        apply.command = [root.helperPath, "set-unlock", on ? "on" : "off"];
        apply.running = true;
    }

    Process {
        id: query
        command: [root.helperPath, "status"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const parsed = JSON.parse(this.text);
                    root.readerPresent = parsed.reader === true;
                    root.readerName = String(parsed.readerName ?? "");
                    root.enrolled = Array.isArray(parsed.enrolled) ? parsed.enrolled : [];
                    root.pamEnabled = parsed.pamEnabled === true;
                    if (String(parsed.error ?? "") !== "")
                        root.lastError = String(parsed.error);
                } catch (error) {
                    root.readerPresent = false;
                    root.lastError = "Could not read the fingerprint helper's output.";
                    console.warn("Fingerprint: could not parse helper output:", error);
                }
                root.scanned = true;
            }
        }
    }

    Process {
        id: apply
        stderr: StdioCollector {
            // A dismissed polkit prompt is a normal outcome on this page, not
            // a failure to report.
            onStreamFinished: {
                const text = this.text.trim();
                if (text !== "" && !/dismissed|not authorized/i.test(text))
                    root.lastError = text;
            }
        }
        // Re-read rather than assuming: authselect may refuse, and the
        // prompt may have been dismissed.
        onExited: {
            root.busy = false;
            root.refresh();
        }
    }
}
