"""Pure parser/validator for hotkey strings (no ctypes, no Win32).

Format: ``"<ctrl>+<shift>+<space>"`` — parts are ``+``-separated, and each
token may optionally be wrapped in angle brackets.  Modifier tokens are
recognised by alias (ctrl/control, shift, alt, win/meta/super/cmd);
everything else must resolve to a virtual-key code via ``_VK_TABLE``.
"""
from __future__ import annotations

from dataclasses import dataclass

# ─── Modifier flags (duplicated here to keep parsing pure) ───────────────
MOD_ALT = 0x0001
MOD_CONTROL = 0x0002
MOD_SHIFT = 0x0004
MOD_WIN = 0x0008


_MODIFIER_ALIASES: dict[str, int] = {
    "ctrl": MOD_CONTROL,
    "control": MOD_CONTROL,
    "shift": MOD_SHIFT,
    "alt": MOD_ALT,
    "win": MOD_WIN,
    "meta": MOD_WIN,
    "super": MOD_WIN,
    "cmd": MOD_WIN,
}


def _build_vk_table() -> dict[str, int]:
    table: dict[str, int] = {}

    # a-z → 0x41-0x5A (uppercase ASCII)
    for i, ch in enumerate("abcdefghijklmnopqrstuvwxyz"):
        table[ch] = 0x41 + i

    # 0-9 → 0x30-0x39 (ASCII digit)
    for d in range(10):
        table[str(d)] = 0x30 + d

    # F1-F24 → 0x70-0x87
    for i in range(1, 25):
        table[f"f{i}"] = 0x70 + (i - 1)

    # Named keys.
    named = {
        "space": 0x20,
        "enter": 0x0D,
        "return": 0x0D,
        "tab": 0x09,
        "esc": 0x1B,
        "escape": 0x1B,
        "backspace": 0x08,
        "delete": 0x2E,
        "del": 0x2E,
        "insert": 0x2D,
        "ins": 0x2D,
        "home": 0x24,
        "end": 0x23,
        "pageup": 0x21,
        "pgup": 0x21,
        "pagedown": 0x22,
        "pgdn": 0x22,
        "up": 0x26,
        "down": 0x28,
        "left": 0x25,
        "right": 0x27,
    }
    table.update(named)
    return table


_VK_TABLE: dict[str, int] = _build_vk_table()


# ─── Pretty-print helpers ────────────────────────────────────────────────
_MOD_PRETTY: list[tuple[int, str]] = [
    (MOD_CONTROL, "Ctrl"),
    (MOD_ALT, "Alt"),
    (MOD_SHIFT, "Shift"),
    (MOD_WIN, "Win"),
]

_VK_PRETTY = {vk: name.capitalize() for name, vk in _VK_TABLE.items()}


def _pretty(mods: int, vk: int) -> str:
    parts = [label for flag, label in _MOD_PRETTY if mods & flag]
    key = _VK_PRETTY.get(vk, f"VK_{vk:#04x}")
    parts.append(key)
    return "+".join(parts)


@dataclass(frozen=True)
class HotkeyParse:
    """Result of parsing a hotkey string.

    If ``error`` is not None, ``modifiers`` and ``vk`` are undefined
    (both 0) and should not be used.
    """
    modifiers: int
    vk: int
    pretty: str
    error: str | None


def parse_hotkey(s: str) -> HotkeyParse:
    """Parse a hotkey string into modifiers + virtual-key code.

    Returns a ``HotkeyParse`` — inspect ``.error`` to detect failure.
    """
    if s is None or not str(s).strip():
        return HotkeyParse(0, 0, "", "Empty hotkey")

    mods = 0
    vk: int | None = None

    for raw in s.split("+"):
        token = raw.strip().lower()
        if token.startswith("<") and token.endswith(">"):
            token = token[1:-1].strip()
        if not token:
            continue

        if token in _MODIFIER_ALIASES:
            mods |= _MODIFIER_ALIASES[token]
            continue

        if token in _VK_TABLE:
            if vk is not None:
                return HotkeyParse(
                    0, 0, "", f"Multiple non-modifier keys in hotkey: '{s}'",
                )
            vk = _VK_TABLE[token]
            continue

        return HotkeyParse(0, 0, "", f"Unknown key '{raw.strip()}' in hotkey")

    if vk is None:
        return HotkeyParse(0, 0, "", "Hotkey must contain a non-modifier key")

    err = validate_hotkey(mods, vk)
    if err:
        return HotkeyParse(0, 0, "", err)

    return HotkeyParse(mods, vk, _pretty(mods, vk), None)


def validate_hotkey(mods: int, vk: int) -> str | None:
    """Return an error message if ``(mods, vk)`` is not safe to register.

    - Rejects Ctrl+Alt+Del (0x2E with Ctrl+Alt): fully reserved by the OS.
    - Rejects Win+L (0x4C with Win): OS-level lock shortcut that apps
      cannot override.
    - F12 is accepted but flagged with a log note (returned via ``None``
      here — caller can log).  We do not block it.
    """
    # Ctrl+Alt+Del — reserved system shortcut.
    if (mods & MOD_CONTROL) and (mods & MOD_ALT) and vk == 0x2E:
        return "Ctrl+Alt+Del is reserved by Windows"

    # Win+L — OS-level workstation lock.
    if (mods & MOD_WIN) and vk == 0x4C:
        return "Win+L is reserved by Windows (lock workstation)"

    return None
