pragma Singleton

// The GPUs that can report utilisation.
//
// The vitals readout needs one specific sysfs file, and card numbering is
// neither stable across machines nor meaningful to a person -- this machine has
// two amdgpu cards, one discrete and one integrated, and picking a number
// blindly measures whichever the kernel enumerated first.
//
// Enumerated on demand rather than polled: hardware does not appear while you
// are looking at a settings page.

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

Singleton {
    id: root

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

    // [{ card, path, name, busy }]
    property var devices: []
    property bool scanning: false
    property string lastError: ""

    readonly property string selectedPath: DesktopPreferences.get("gpuBusyPath")

    readonly property var selected: root.devices.find(device => device.path === root.selectedPath) ?? null

    // True when a GPU is stored that this machine does not have -- after moving
    // the settings file between machines, say.
    readonly property bool selectionMissing: root.devices.length > 0 && root.selected === null

    Process {
        id: scan
        command: [root.helperPath]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const parsed = JSON.parse(this.text);
                    root.devices = Array.isArray(parsed) ? parsed : [];
                    root.lastError = "";
                } catch (error) {
                    root.devices = [];
                    root.lastError = "The graphics devices could not be read.";
                }
                root.scanning = false;
            }
        }
        onExited: (exitCode, exitStatus) => {
            root.scanning = false;
            if (exitCode !== 0)
                root.lastError = "The graphics devices could not be read.";
        }
    }

    Component.onCompleted: root.refresh()

    function refresh(): void {
        if (scan.running)
            return;
        root.scanning = true;
        scan.running = true;
    }

    // Only a path this machine actually reported is accepted, so a hand-edited
    // settings file cannot point the readout at an arbitrary file.
    function select(path: string): bool {
        if (!root.devices.some(device => device.path === path)) {
            root.lastError = "That graphics device is not present.";
            return false;
        }
        if (!DesktopPreferences.set("gpuBusyPath", path)) {
            root.lastError = "That graphics device could not be saved.";
            return false;
        }
        root.lastError = "";
        return true;
    }

    // "AMD ... [Radeon RX 7700 XT / 7800 XT] (rev c8)" is what lspci gives; the
    // bracketed marketing name is the part anyone recognises.
    function shortName(name: string): string {
        const bracketed = String(name).match(/\[([^\]]+)\]\s*(?:\(rev[^)]*\))?\s*$/);
        return bracketed ? bracketed[1] : String(name).replace(/\s*\(rev[^)]*\)\s*$/, "");
    }
}
