from __future__ import annotations
import sys
import threading

try:
    import keyboard as keyboard_win
except Exception:
    keyboard_win = None

try:
    from pynput import keyboard as pynput_keyboard
except Exception:
    pynput_keyboard = None


SPECIAL_KEYS = {}
if pynput_keyboard is not None:
    SPECIAL_KEYS = {
        pynput_keyboard.Key.f1: 'F1', pynput_keyboard.Key.f2: 'F2', pynput_keyboard.Key.f3: 'F3',
        pynput_keyboard.Key.f4: 'F4', pynput_keyboard.Key.f5: 'F5', pynput_keyboard.Key.f6: 'F6',
        pynput_keyboard.Key.f7: 'F7', pynput_keyboard.Key.f8: 'F8', pynput_keyboard.Key.f9: 'F9',
        pynput_keyboard.Key.f10: 'F10', pynput_keyboard.Key.f11: 'F11', pynput_keyboard.Key.f12: 'F12',
        pynput_keyboard.Key.space: 'SPACE', pynput_keyboard.Key.enter: 'ENTER',
        pynput_keyboard.Key.tab: 'TAB', pynput_keyboard.Key.esc: 'ESC',
        pynput_keyboard.Key.backspace: 'BACKSPACE', pynput_keyboard.Key.delete: 'DELETE',
        pynput_keyboard.Key.home: 'HOME', pynput_keyboard.Key.end: 'END',
        pynput_keyboard.Key.page_up: 'PAGE UP', pynput_keyboard.Key.page_down: 'PAGE DOWN',
        pynput_keyboard.Key.up: 'UP', pynput_keyboard.Key.down: 'DOWN',
        pynput_keyboard.Key.left: 'LEFT', pynput_keyboard.Key.right: 'RIGHT',
    }

ALIASES = {
    'KEY.ALT_L': 'LEFT ALT', 'KEY.ALT_R': 'RIGHT ALT', 'KEY.ALT': 'ALT',
    'KEY.CTRL_L': 'LEFT CTRL', 'KEY.CTRL_R': 'RIGHT CTRL', 'KEY.CTRL': 'CTRL',
    'KEY.SHIFT_L': 'LEFT SHIFT', 'KEY.SHIFT_R': 'RIGHT SHIFT', 'KEY.SHIFT': 'SHIFT',
    'KEY.CMD_L': 'LEFT WIN', 'KEY.CMD_R': 'RIGHT WIN', 'KEY.CMD': 'WIN',
}


def normalize_key(key) -> str | None:
    if key in SPECIAL_KEYS:
        return SPECIAL_KEYS[key]
    try:
        char = key.char
    except Exception:
        char = None
    if char:
        return char.upper()
    raw = str(key).upper()
    return ALIASES.get(raw, raw.replace('KEY.', '').replace('_', ' '))


def canonical_key_name(name: str) -> str:
    raw = (name or '').strip().upper()
    return ALIASES.get(raw, raw.replace('KEY.', '').replace('_', ' '))


def keyboard_module_name(name: str) -> str:
    return canonical_key_name(name).lower().replace(' ', '_')


class GlobalPTT:
    def __init__(self, key_name, on_press, on_release):
        self.key_name = canonical_key_name(key_name)
        self.on_press_cb = on_press
        self.on_release_cb = on_release
        self.down = False
        self.listener = None
        self.hook = None
        self.backend = 'none'
        self._lock = threading.Lock()

    def _press_once(self):
        with self._lock:
            if self.down:
                return
            self.down = True
        self.on_press_cb()

    def _release_once(self):
        with self._lock:
            if not self.down:
                return
            self.down = False
        self.on_release_cb()

    def start(self):
        self.stop()

        # On Windows the keyboard package is more reliable for hold/release F-keys.
        if sys.platform.startswith('win') and keyboard_win is not None:
            key = keyboard_module_name(self.key_name)
            def handler(event):
                if event.event_type == 'down':
                    self._press_once()
                elif event.event_type == 'up':
                    self._release_once()
            self.hook = keyboard_win.hook_key(key, handler, suppress=False)
            self.backend = 'keyboard'
            return self.backend

        if pynput_keyboard is None:
            raise RuntimeError('Global keyboard backend is unavailable.')

        def press(key):
            if normalize_key(key) == self.key_name:
                self._press_once()

        def release(key):
            if normalize_key(key) == self.key_name:
                self._release_once()

        self.listener = pynput_keyboard.Listener(on_press=press, on_release=release)
        self.listener.start()
        self.backend = 'pynput'
        return self.backend

    def stop(self):
        if self.hook is not None and keyboard_win is not None:
            try:
                keyboard_win.unhook(self.hook)
            except Exception:
                pass
        self.hook = None

        listener = self.listener
        self.listener = None
        if listener is not None:
            try:
                listener.stop()
            except Exception:
                pass
        with self._lock:
            self.down = False
        self.backend = 'none'
