# key_captor.py
"""
Capture des combos clavier pour agent_v0.

v0 :
- écoute globale clavier via pynput
- maintient un set de touches "modificateurs" enfoncées (CTRL, ALT, SHIFT, CMD)
- quand une touche non-modif est pressée avec au moins un modif,
  appelle un callback (keys: List[str]) du type ["CTRL", "F"]
"""

from __future__ import annotations

from typing import Callable, List, Set

from pynput import keyboard

KeyComboCallback = Callable[[List[str]], None]


MODIFIER_MAP = {
    keyboard.Key.ctrl_l: "CTRL",
    keyboard.Key.ctrl_r: "CTRL",
    keyboard.Key.alt_l: "ALT",
    keyboard.Key.alt_r: "ALT",
    keyboard.Key.shift: "SHIFT",
    keyboard.Key.shift_l: "SHIFT",
    keyboard.Key.shift_r: "SHIFT",
    keyboard.Key.cmd: "CMD",
    keyboard.Key.cmd_l: "CMD",
    keyboard.Key.cmd_r: "CMD",
}


class KeyCaptor:
    """
    Capture les combos clavier globaux et les relaie à un callback.
    """

    def __init__(self, on_key_combo: KeyComboCallback) -> None:
        """
        :param on_key_combo: fonction appelée sur combo clavier,
                             signature (keys: List[str])
        """
        self._on_key_combo = on_key_combo
        self._listener: keyboard.Listener | None = None
        self._mods_pressed: Set[str] = set()

    def start(self) -> None:
        """Démarre l'écoute globale clavier."""
        if self._listener is not None:
            return

        self._listener = keyboard.Listener(
            on_press=self._handle_press,
            on_release=self._handle_release,
        )
        self._listener.start()

    def stop(self) -> None:
        """Arrête l'écoute globale clavier."""
        if self._listener is not None:
            self._listener.stop()
            self._listener = None
            self._mods_pressed.clear()

    # --- callbacks internes ---

    def _handle_press(self, key: keyboard.Key | keyboard.KeyCode) -> None:
        # Gestion des mods
        mod_name = MODIFIER_MAP.get(key)
        if mod_name:
            self._mods_pressed.add(mod_name)
            return

        # Ici : touche non-modif
        if not self._mods_pressed:
            # Pas de modif → on ignore (on ne log pas la frappe "normale")
            return

        # On construit la liste des touches
        keys = sorted(self._mods_pressed)

        # Nom de la touche principale
        main_key = self._key_to_str(key)
        if main_key is None:
            return

        keys.append(main_key)

        # Appel du callback
        try:
            self._on_key_combo(keys)
        except Exception:
            # On évite de casser le listener en cas d'erreur dans le callback
            pass

    def _handle_release(self, key: keyboard.Key | keyboard.KeyCode) -> None:
        # Retirer les modifs qui ne sont plus pressées
        mod_name = MODIFIER_MAP.get(key)
        if mod_name and mod_name in self._mods_pressed:
            self._mods_pressed.discard(mod_name)

    @staticmethod
    def _key_to_str(key: keyboard.Key | keyboard.KeyCode) -> str | None:
        # Touche "normale" (KeyCode avec .char)
        if isinstance(key, keyboard.KeyCode):
            if key.char:
                return key.char.upper()
            return None

        # Quelques touches "spéciales" qu'on peut vouloir logguer
        special_map = {
            keyboard.Key.enter: "ENTER",
            keyboard.Key.tab: "TAB",
            keyboard.Key.esc: "ESC",
            keyboard.Key.space: "SPACE",
        }
        if key in special_map:
            return special_map[key]

        # Sinon, on ignore
        return None
