#!/usr/bin/env python3
# FWS — affiche les raccourcis clavier RÉELS de la session (bouton ⌨ de la
# barre ou Super+K). Universel :
#   • Hyprland : « hyprctl binds -j » = les binds effectivement chargés
#     (descriptions des bindd) — affichage wofi (Wayland).
#   • i3       : parse ~/.config/i3/config (bindsym, variables résolues)
#     — affichage rofi (X11).
# Aucune dépendance hors python3 + wofi/rofi (déjà installés avec le bureau).

import json
import os
import re
import subprocess
import sys

_WOFI_STYLE = "/usr/local/share/fws/wofi-keys.css"

# Bits de modmask Hyprland (masques X11) dans l'ordre d'affichage voulu.
_MODS = [(64, "SUPER"), (4, "CTRL"), (8, "ALT"), (1, "SHIFT")]

_KEY_LABELS = {
    "mouse:272": "clic gauche",
    "mouse:273": "clic droit",
    "mouse:274": "clic molette",
    "mouse_down": "molette bas",
    "mouse_up": "molette haut",
    "button4": "molette haut",
    "button5": "molette bas",
    "return": "Entrée",
    "space": "Espace",
    "escape": "Échap",
    "tab": "Tab",
    "left": "←",
    "right": "→",
    "up": "↑",
    "down": "↓",
    "print": "Impr écran",
}


def _label(key):
    return _KEY_LABELS.get(key.lower(), key)


# --- Hyprland : binds réels via hyprctl ------------------------------------
def hyprland_lines():
    raw = subprocess.check_output(["hyprctl", "binds", "-j"], text=True)
    lines, seen = [], set()
    for bind in json.loads(raw):
        parts = [name for bit, name in _MODS if bind.get("modmask", 0) & bit]
        key = bind.get("key") or (f"code {bind['keycode']}" if bind.get("keycode") else "")
        parts.append(_label(key))
        combo = " + ".join(p for p in parts if p)
        if not combo or combo in seen:
            continue
        seen.add(combo)
        desc = (bind.get("description") or "").strip() \
            or f"{bind.get('dispatcher', '?')} {(bind.get('arg') or '').strip()}".strip()
        lines.append(f"{combo:<24}  {desc}")
    return lines


# --- i3 : parse de la config (variables résolues) ----------------------------
def i3_lines():
    path = os.path.expanduser("~/.config/i3/config")
    variables = {}
    lines, seen = [], set()
    with open(path, encoding="utf-8", errors="replace") as fh:
        content = fh.readlines()
    for line in content:
        m = re.match(r"\s*set\s+(\$\S+)\s+(.+)$", line)
        if m:
            variables[m.group(1)] = m.group(2).strip()
    def resolve(text):
        # $var les plus longs d'abord ($filemanager avant $file…).
        for var in sorted(variables, key=len, reverse=True):
            text = text.replace(var, variables[var])
        return text
    for line in content:
        m = re.match(r"\s*bindsym\s+(?:--\S+\s+)*(\S+)\s+(.+)$", line)
        if not m:
            continue
        combo_raw, action = m.groups()
        combo_raw = resolve(combo_raw).replace("Mod4", "Super").replace("Mod1", "Alt")
        combo = " + ".join(_label(k) for k in combo_raw.split("+"))
        if combo in seen:
            continue
        seen.add(combo)
        action = resolve(action)
        action = re.sub(r"^exec\s+(--no-startup-id\s+)?", "", action).strip().strip('"')
        lines.append(f"{combo:<24}  {action}")
    return lines


def show(lines):
    if not lines:
        lines = ["(aucun raccourci trouvé)"]
    text = "\n".join(lines)
    if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
        cmd = ["wofi", "--dmenu", "--prompt", "Raccourcis clavier — taper pour filtrer",
               "--width", "820", "--height", "600", "--insensitive",
               "--cache-file", "/dev/null"]
        if os.path.exists(_WOFI_STYLE):
            cmd += ["--style", _WOFI_STYLE]
    else:
        # X11/i3 : rofi en dmenu, style assorti (mono pour l'alignement).
        cmd = ["rofi", "-dmenu", "-i", "-p", "Raccourcis clavier",
               "-no-custom",
               "-theme-str", 'window { width: 880px; } listview { lines: 18; }',
               "-theme-str", '* { font: "Noto Sans Mono 11"; }']
    subprocess.run(cmd, input=text, text=True, check=False)


def main():
    try:
        if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
            lines = hyprland_lines()
        else:
            lines = i3_lines()
    except Exception as exc:  # noqa: BLE001 — best effort, jamais de crash muet
        subprocess.run(["notify-send", "Raccourcis FWS", f"Erreur : {exc}"],
                       check=False)
        return 1
    show(lines)
    return 0


if __name__ == "__main__":
    sys.exit(main())
