// Captures a key chord for rebinding.
//
// Shown in place of a shortcut's chord while it is being changed. It takes
// keyboard focus, waits for a non-modifier key, and reports the chord in the
// form hypr/keybinds.lua uses.
//
// Modifier-only presses are ignored rather than accepted, because every press
// of a chord passes through them: holding Super to type Super+K would otherwise
// be captured as "SUPER" the moment the modifier went down.
//
// Escape cancels. Not every Qt key has a keysym name Hyprland would accept, so
// an unmapped key is refused with a message rather than written as something
// that would silently fail to bind.

import QtQuick
import qs.config

FocusScope {
    id: root

    signal captured(string chord)
    signal cancelled

    property string message: ""

    implicitWidth: 230
    implicitHeight: 30

    // Qt key codes to the keysym names Hyprland expects. Letters and digits
    // fall out of the ASCII range; these are the rest that come up in practice.
    readonly property var namedKeys: ({
        0x01000000: "Escape",
        0x01000001: "Tab",
        0x01000004: "Return",
        0x01000005: "Return",
        0x01000003: "BackSpace",
        0x01000006: "Insert",
        0x01000007: "Delete",
        0x01000010: "Home",
        0x01000011: "End",
        0x01000016: "Page_Up",
        0x01000017: "Page_Down",
        0x01000012: "left",
        0x01000013: "up",
        0x01000014: "right",
        0x01000015: "down",
        0x20: "space",
        0x2c: "comma",
        0x2e: "period",
        0x2f: "slash",
        0x3b: "semicolon",
        0x27: "apostrophe",
        0x5b: "bracketleft",
        0x5d: "bracketright",
        0x5c: "backslash",
        0x60: "grave",
        0x2d: "minus",
        0x3d: "equal",
        0x01000009: "Print"
    })

    function keysymFor(key: int): string {
        if (key >= 0x41 && key <= 0x5a)            // A-Z
            return String.fromCharCode(key);
        if (key >= 0x30 && key <= 0x39)            // 0-9
            return String.fromCharCode(key);
        if (key >= 0x01000030 && key <= 0x0100003b) // F1-F12
            return "F" + (key - 0x01000030 + 1);
        return root.namedKeys[key] ?? "";
    }

    function isModifierOnly(key: int): bool {
        return key === 0x01000020 || key === 0x01000021 || key === 0x01000022
            || key === 0x01000023 || key === 0x01000024;
    }

    Rectangle {
        anchors.fill: parent
        radius: 9
        color: Theme.alpha(Theme.accent, 0.14)
        border.width: 1
        border.color: Theme.alpha(Theme.accent, 0.5)

        Text {
            anchors.centerIn: parent
            width: parent.width - 16
            horizontalAlignment: Text.AlignHCenter
            elide: Text.ElideRight
            text: root.message !== "" ? root.message : "Press a shortcut… Esc to cancel"
            color: root.message !== "" ? Theme.warn : Theme.fg
            font.family: Theme.fontFamily
            font.pixelSize: Theme.fontSizeSmall
        }
    }

    Keys.onPressed: event => {
        event.accepted = true;

        if (event.key === 0x01000000) {   // Escape
            root.cancelled();
            return;
        }
        if (root.isModifierOnly(event.key))
            return;

        const keysym = root.keysymFor(event.key);
        if (keysym === "") {
            root.message = "That key cannot be used";
            return;
        }

        const parts = [];
        if (event.modifiers & Qt.MetaModifier) parts.push("SUPER");
        if (event.modifiers & Qt.ControlModifier) parts.push("CTRL");
        if (event.modifiers & Qt.AltModifier) parts.push("ALT");
        if (event.modifiers & Qt.ShiftModifier) parts.push("SHIFT");

        if (parts.length === 0 && keysym.length === 1) {
            // A bare letter or digit would swallow ordinary typing.
            root.message = "Add a modifier";
            return;
        }

        parts.push(keysym);
        root.captured(parts.join(" + "));
    }

    onActiveFocusChanged: {
        if (!activeFocus)
            root.cancelled();
    }
}
