pragma Singleton

// Installed fonts, split by what they can actually be used for.
//
// Two different questions with different answers: which families are readable
// as interface text, and which carry the Nerd Font glyphs the shell draws its
// icons with. Offering one list for both is how you end up with a desktop full
// of tofu, so they stay separate.
//
// Enumerated once on demand. Fonts do not appear while you are looking at a
// settings page, and fc-list over a few hundred families is not free.

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

Singleton {
    id: root

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

    property var interfaceFonts: []
    property var monospaceFonts: []
    property var iconFonts: []
    property bool scanning: false
    property string lastError: ""

    readonly property string interfaceFont: DesktopPreferences.get("interfaceFont")
    readonly property string iconFont: DesktopPreferences.get("iconFont")

    readonly property bool loaded: root.interfaceFonts.length > 0

    // True when the stored family is not installed. fontconfig silently
    // substitutes something else, so the shell keeps working and quietly stops
    // looking the way it was configured to -- worth saying out loud.
    readonly property bool interfaceMissing: root.loaded
        && root.interfaceFonts.indexOf(root.interfaceFont) < 0
    readonly property bool iconMissing: root.iconFonts.length > 0
        && root.iconFonts.indexOf(root.iconFont) < 0

    Process {
        id: scan
        command: [root.helperPath]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const parsed = JSON.parse(this.text);
                    root.interfaceFonts = parsed.interface ?? [];
                    root.monospaceFonts = parsed.monospace ?? [];
                    root.iconFonts = parsed.icons ?? [];
                    root.lastError = "";
                } catch (error) {
                    root.lastError = "The installed fonts could not be read.";
                }
                root.scanning = false;
            }
        }
        onExited: (exitCode, exitStatus) => {
            root.scanning = false;
            if (exitCode !== 0)
                root.lastError = "The installed fonts could not be read.";
        }
    }

    Component.onCompleted: root.refresh()

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

    // Only a family this machine reported is accepted, so a hand-edited
    // settings file cannot put arbitrary text where a font name belongs -- it
    // reaches hl.config as a string on the compositor side.
    function setInterface(family: string): bool {
        if (root.interfaceFonts.indexOf(family) < 0) {
            root.lastError = "That font is not installed.";
            return false;
        }
        return root.store("interfaceFont", family);
    }

    function setIcon(family: string): bool {
        if (root.iconFonts.indexOf(family) < 0) {
            root.lastError = "That font does not carry icon glyphs.";
            return false;
        }
        return root.store("iconFont", family);
    }

    function store(key: string, family: string): bool {
        if (!DesktopPreferences.set(key, family)) {
            root.lastError = "That font name could not be saved.";
            return false;
        }
        root.lastError = "";
        return true;
    }
}
