"""Global modifier-hold hotkey via Quartz CGEventTap.

Listens to kCGEventFlagsChanged. For 'right_option' mode we watch keycode 61 (right ⌥)
and fire press / release callbacks. Same shape works for left option (58), control,
shift, command, and Fn (the Fn flag is kCGEventFlagMaskSecondaryFn = 0x800000 but
its keycode varies by Mac model).

Run install() on the main thread BEFORE the rumps run loop starts; the event source
is added to the current run loop and stays alive while the app runs.
"""

from __future__ import annotations

from typing import Callable

from Quartz import (
    CFMachPortCreateRunLoopSource,
    CFRunLoopAddSource,
    CFRunLoopGetCurrent,
    CGEventGetFlags,
    CGEventGetIntegerValueField,
    CGEventMaskBit,
    CGEventTapCreate,
    CGEventTapEnable,
    kCFRunLoopCommonModes,
    kCGEventFlagMaskAlternate,
    kCGEventFlagMaskCommand,
    kCGEventFlagMaskControl,
    kCGEventFlagMaskSecondaryFn,
    kCGEventFlagMaskShift,
    kCGEventFlagsChanged,
    kCGEventKeyDown,
    kCGEventTapDisabledByTimeout,
    kCGEventTapDisabledByUserInput,
    kCGEventTapOptionDefault,
    kCGEventTapOptionListenOnly,
    kCGHeadInsertEventTap,
    kCGKeyboardEventKeycode,
    kCGSessionEventTap,
)

KEYCODE_RIGHT_OPTION = 61
KEYCODE_LEFT_OPTION = 58
KEYCODE_FN_GLOBE = 63  # on most M-series Macs; older Intel may differ

MODE_SPECS = {
    "right_option": {"keycode": KEYCODE_RIGHT_OPTION, "flag_mask": kCGEventFlagMaskAlternate},
    "left_option": {"keycode": KEYCODE_LEFT_OPTION, "flag_mask": kCGEventFlagMaskAlternate},
    "fn": {"keycode": KEYCODE_FN_GLOBE, "flag_mask": kCGEventFlagMaskSecondaryFn},
}


class HotkeyTap:
    """Listens to modifier-hold + (optionally) double-tap on the same modifier.

    - hold:        on_press fires when the modifier goes down, on_release when it goes up
    - double-tap:  on_double_tap (optional) fires when the modifier is pressed twice
                   within DOUBLE_TAP_WINDOW seconds. When this happens we suppress the
                   normal on_press/on_release for that second press so we don't both
                   toggle and start a hold simultaneously.
    """
    # Seconds between two presses to count as a double-tap. 0.40 was the
    # original — felt too tight in practice (user had to tap noticeably
    # faster than a natural double-click). macOS's default double-click
    # threshold is ~0.5s; we go a little wider to leave room for tactile
    # variation on the modifier keys, which are pressed by the side of
    # the thumb rather than a fingertip and feel "slower".
    DOUBLE_TAP_WINDOW = 0.60

    def __init__(self, mode: str,
                 on_press: Callable[[], None],
                 on_release: Callable[[], None],
                 on_double_tap: Callable[[], None] | None = None):
        if mode not in MODE_SPECS:
            raise ValueError(f"unknown hotkey mode: {mode}")
        spec = MODE_SPECS[mode]
        self.mode = mode
        self.keycode = spec["keycode"]
        self.flag_mask = spec["flag_mask"]
        self.on_press = on_press
        self.on_release = on_release
        self.on_double_tap = on_double_tap
        self._held = False
        self._tap = None
        self._source = None
        self._last_press_at: float = 0.0
        self._suppress_next_release: bool = False

    def _callback(self, proxy, event_type, event, refcon):  # noqa: ARG002
        # CR #13 — macOS disables an event tap whose callback was too
        # slow (TapDisabledByTimeout) or that the user toggled off
        # (TapDisabledByUserInput). The system delivers a single
        # synthetic event of these types to tell us. Without re-enabling,
        # the hotkey dies until the next app launch — Anastasia's other
        # "капсула не отвечает" symptom. Re-enable and return without
        # treating the synthetic event as a real key press.
        if event_type in (kCGEventTapDisabledByTimeout,
                          kCGEventTapDisabledByUserInput):
            try:
                if self._tap is not None:
                    CGEventTapEnable(self._tap, True)
                    print(f"[hotkey] tap re-enabled after event_type="
                          f"{int(event_type)}", flush=True)
            except Exception as e:
                print(f"[hotkey] tap re-enable failed: {e}", flush=True)
            # CR (2026-07-04) — force-reset `_held` after tap re-enable.
            # If macOS disabled the tap WHILE the user was holding
            # Right Option, the release-event never reached us — we
            # think it's still held. Next physical press then hits
            # the `is_down and not self._held` guard as False (because
            # _held is stuck True from before) and gets silently
            # dropped. User had to release + repress + release + repress
            # to "unstick" it. Now: any tap-disabled/re-enable resets
            # our belief about hold state; if the modifier is still
            # physically down, the very next flagsChanged event will
            # re-establish it correctly.
            if self._held:
                print("[hotkey] force-clearing _held after tap re-enable",
                      flush=True)
                self._held = False
                # Also fire on_release so downstream state machine
                # (recorder, overlay) doesn't stay in "listening" forever.
                # If the modifier was still down, the immediate next
                # press event will start a fresh recording.
                if not self._suppress_next_release:
                    import threading as _th
                    def _release_fallback():
                        try:
                            self.on_release()
                        except Exception as e:
                            print(f"[hotkey] on_release (fallback) error: {e}",
                                  flush=True)
                    _th.Thread(target=_release_fallback, daemon=True).start()
                self._suppress_next_release = False
            return event
        try:
            kc = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)
            if kc != self.keycode:
                return event
            flags = CGEventGetFlags(event)
            is_down = bool(flags & self.flag_mask)
            # CR (2026-07-04) — dispatch on_press / on_release on a
            # background thread and return the event immediately.
            # macOS disables the event-tap if the callback blocks the
            # thread for more than ~1 s (kCGEventTapDisabledByTimeout).
            # Even though on_press/on_release try to be fast, they
            # spawn the audio helper, acquire recorder locks, and can
            # contend with an active MLX transcription for the GIL —
            # under Local Mode that stall was long enough to trip
            # the timeout. User saw: press Right Option, nothing
            # happens, release, press again, THEN it starts recording.
            # Threads keep the callback path <1 ms.
            import threading as _th
            if is_down and not self._held:
                self._held = True
                import time as _t
                now = _t.time()
                is_double_tap = (
                    self.on_double_tap is not None
                    and (now - self._last_press_at) < self.DOUBLE_TAP_WINDOW
                )
                self._last_press_at = now
                if is_double_tap:
                    self._suppress_next_release = True
                    def _dt():
                        try:
                            self.on_double_tap()
                        except Exception as e:
                            print(f"[hotkey] on_double_tap error: {e}",
                                  flush=True)
                    _th.Thread(target=_dt, daemon=True).start()
                else:
                    def _press():
                        try:
                            self.on_press()
                        except Exception as e:
                            print(f"[hotkey] on_press error: {e}",
                                  flush=True)
                    _th.Thread(target=_press, daemon=True).start()
            elif not is_down and self._held:
                self._held = False
                if self._suppress_next_release:
                    self._suppress_next_release = False
                else:
                    def _release():
                        try:
                            self.on_release()
                        except Exception as e:
                            print(f"[hotkey] on_release error: {e}",
                                  flush=True)
                    _th.Thread(target=_release, daemon=True).start()
        except Exception as e:
            print(f"[hotkey] callback error: {e}")
        return event

    def install(self) -> None:
        mask = CGEventMaskBit(kCGEventFlagsChanged)
        self._tap = CGEventTapCreate(
            kCGSessionEventTap,
            kCGHeadInsertEventTap,
            kCGEventTapOptionListenOnly,
            mask,
            self._callback,
            None,
        )
        if not self._tap:
            raise RuntimeError(
                "CGEventTapCreate failed. Grant Accessibility permission to this app "
                "(System Settings → Privacy & Security → Accessibility)."
            )
        self._source = CFMachPortCreateRunLoopSource(None, self._tap, 0)
        CFRunLoopAddSource(CFRunLoopGetCurrent(), self._source, kCFRunLoopCommonModes)
        CGEventTapEnable(self._tap, True)
        print(f"[hotkey] installed: mode={self.mode} keycode={self.keycode}")


# Modifier-bit masks that PasteHotkey cares about (cmd / shift / control / alt).
# Fn is intentionally excluded — modifier-hold hotkey uses Fn separately.
PASTE_MODIFIER_BITS = (
    kCGEventFlagMaskCommand
    | kCGEventFlagMaskShift
    | kCGEventFlagMaskControl
    | kCGEventFlagMaskAlternate
)


class PasteHotkey:
    """Single-press keystroke shortcut (modifier combo + key). Fires a callback and
    consumes the event so the focused app doesn't also receive it. Separate event
    tap from the modifier-hold HotkeyTap so consumption semantics are isolated."""

    def __init__(self, keycode: int, modifiers_mask: int, on_fire: Callable[[], None]):
        self.keycode = keycode
        self.modifiers_mask = modifiers_mask & PASTE_MODIFIER_BITS
        self.on_fire = on_fire
        self._tap = None
        self._source = None

    def _callback(self, proxy, event_type, event, refcon):  # noqa: ARG002
        # CR #13 — same tap-disabled recovery as HotkeyTap above. Without
        # this the paste hotkey silently dies for the rest of the session
        # the first time the system decides we were too slow.
        if event_type in (kCGEventTapDisabledByTimeout,
                          kCGEventTapDisabledByUserInput):
            try:
                if self._tap is not None:
                    CGEventTapEnable(self._tap, True)
                    print(f"[paste-hotkey] tap re-enabled after event_type="
                          f"{int(event_type)}", flush=True)
            except Exception as e:
                print(f"[paste-hotkey] tap re-enable failed: {e}", flush=True)
            return event
        try:
            kc = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)
            if kc != self.keycode:
                return event
            flags = CGEventGetFlags(event)
            if (flags & PASTE_MODIFIER_BITS) != self.modifiers_mask:
                return event
            # CR (2026-07-04) — dispatch on_fire on a bg thread so the
            # event tap callback returns immediately. Same tap-timeout
            # class of bug as HotkeyTap; see rationale there.
            import threading as _th
            def _fire():
                try:
                    self.on_fire()
                except Exception as e:
                    print(f"[paste-hotkey] callback error: {e}", flush=True)
            _th.Thread(target=_fire, daemon=True).start()
            return None  # consume → focused app doesn't receive Cmd+Opt+V
        except Exception as e:
            print(f"[paste-hotkey] error: {e}")
            return event

    def install(self) -> None:
        mask = CGEventMaskBit(kCGEventKeyDown)
        self._tap = CGEventTapCreate(
            kCGSessionEventTap,
            kCGHeadInsertEventTap,
            kCGEventTapOptionDefault,  # allow consumption
            mask,
            self._callback,
            None,
        )
        if not self._tap:
            raise RuntimeError("CGEventTapCreate failed for paste hotkey")
        self._source = CFMachPortCreateRunLoopSource(None, self._tap, 0)
        CFRunLoopAddSource(CFRunLoopGetCurrent(), self._source, kCFRunLoopCommonModes)
        CGEventTapEnable(self._tap, True)
        print(f"[paste-hotkey] installed: keycode={self.keycode} mods={self.modifiers_mask}")
