pragma Singleton

// The active keyboard layout, for the bar.
//
// Only interesting on a machine with more than one layout configured -- a
// single-layout machine gets no indicator at all, which is GNOME's behavior
// too. "More than one" is read straight from the keyboardLayout preference,
// the same value input.lua feeds the compositor, so no probe can disagree
// with the config.
//
// Hyprland reports the active keymap by DESCRIPTION ("English (US)"), and
// the bar wants the short code ("us"). Deriving one from the other by
// truncation gives wrong answers immediately -- "German" is de, not "ge" --
// so the mapping comes from xkb's own registry (evdev.lst), the file every
// layout chooser is built from. Parsed once; it changes when xkeyboard-config
// updates, which is to say between logins.

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

Singleton {
    id: root

    readonly property var configured: String(DesktopPreferences.get("keyboardLayout"))
        .split(",").map(code => code.trim()).filter(code => code !== "")
    readonly property bool multiple: root.configured.length > 1

    // "English (US)", as Hyprland reports it.
    property string activeKeymap: ""

    // description -> code, from evdev.lst's layout and variant sections.
    property var codeByDescription: ({})

    readonly property string shortLabel: {
        const mapped = root.codeByDescription[root.activeKeymap];
        if (mapped)
            return mapped;
        // Registry miss (a custom keymap): fall back to the first configured
        // code, which at least names something real on this machine.
        return root.configured[0] ?? "";
    }

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

    Process {
        id: query
        command: ["hyprctl", "-j", "devices"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const keyboards = JSON.parse(this.text).keyboards ?? [];
                    const main = keyboards.find(keyboard => keyboard.main) ?? keyboards[0];
                    root.activeKeymap = String(main?.active_keymap ?? "");
                } catch (error) {
                    console.warn("KeyboardLayout: could not parse hyprctl devices:", error);
                }
            }
        }
    }

    FileView {
        path: "/usr/share/X11/xkb/rules/evdev.lst"
        blockLoading: false
        printErrors: false
        onLoaded: {
            const map = {};
            let section = "";
            for (const line of this.text().split("\n")) {
                if (line.startsWith("!")) {
                    section = line.slice(1).trim();
                    continue;
                }
                const trimmed = line.trim();
                if (trimmed === "")
                    continue;
                if (section === "layout") {
                    // "  us              English (US)"
                    const m = /^(\S+)\s+(.+)$/.exec(trimmed);
                    if (m)
                        map[m[2].trim()] = m[1];
                } else if (section === "variant") {
                    // "  dvorak          us: English (Dvorak)"
                    const m = /^\S+\s+(\S+):\s+(.+)$/.exec(trimmed);
                    if (m)
                        map[m[2].trim()] = m[1];
                }
            }
            root.codeByDescription = map;
        }
    }

    // activelayout fires on every switch; configreloaded catches a layout
    // list changed from Settings.
    Connections {
        target: Hyprland
        function onRawEvent(event: var): void {
            if (event.name === "activelayout" || event.name === "configreloaded")
                root.refresh();
        }
    }

    Component.onCompleted: root.refresh()
}
