"""Linux desktop integration for Cthulhu Remote commands."""

from __future__ import annotations

import logging
from typing import Any

import gi

gi.require_version("Atspi", "2.0")
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
from gi.repository import Atspi, Gdk, GLib, Gtk

from cthulhu import speech

logger = logging.getLogger(__name__)


WINDOWS_VK_TO_ATSPI_KEYSYM = {
    0x08: "BackSpace",
    0x09: "Tab",
    0x0D: "Return",
    0x10: "Shift_L",
    0x11: "Control_L",
    0x12: "Alt_L",
    0x13: "Pause",
    0x14: "Caps_Lock",
    0x1B: "Escape",
    0x20: "space",
    0x21: "Page_Up",
    0x22: "Page_Down",
    0x23: "End",
    0x24: "Home",
    0x25: "Left",
    0x26: "Up",
    0x27: "Right",
    0x28: "Down",
    0x2D: "Insert",
    0x2E: "Delete",
    0x5B: "Super_L",
    0x5C: "Super_R",
    0x60: "KP_0",
    0x61: "KP_1",
    0x62: "KP_2",
    0x63: "KP_3",
    0x64: "KP_4",
    0x65: "KP_5",
    0x66: "KP_6",
    0x67: "KP_7",
    0x68: "KP_8",
    0x69: "KP_9",
    0x6A: "KP_Multiply",
    0x6B: "KP_Add",
    0x6D: "KP_Subtract",
    0x6E: "KP_Decimal",
    0x6F: "KP_Divide",
}

for _digit in range(0x30, 0x3A):
    WINDOWS_VK_TO_ATSPI_KEYSYM[_digit] = chr(_digit)
for _letter in range(0x41, 0x5B):
    WINDOWS_VK_TO_ATSPI_KEYSYM[_letter] = chr(_letter).lower()
for _functionKey in range(1, 25):
    WINDOWS_VK_TO_ATSPI_KEYSYM[0x6F + _functionKey] = f"F{_functionKey}"


class LocalMachine:
    """Apply incoming remote commands to the local Linux desktop."""

    def __init__(self, presenter) -> None:
        self._presenter = presenter
        self.isMuted = False

    def speak(self, sequence: Any = None, **kwargs: Any) -> None:
        if self.isMuted:
            return
        text = self._speech_sequence_to_text(sequence)
        if text:
            with speech.suppress_monitor_callbacks():
                speech.speak(text, interrupt=False)

    def cancel_speech(self, **kwargs: Any) -> None:
        if not self.isMuted:
            speech.stop()

    def pause_speech(self, switch: bool = False, **kwargs: Any) -> None:
        if switch:
            speech.stop()

    def set_clipboard_text(self, text: str = "", **kwargs: Any) -> None:
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(text, -1)
        clipboard.store()
        self._presenter("Remote clipboard received")

    def send_key(
        self,
        vk_code: int | None = None,
        keysym: str | None = None,
        pressed: bool | None = True,
        **kwargs: Any,
    ) -> None:
        keyval = self._resolve_keyval(vk_code=vk_code, keysym=keysym)
        if keyval is None:
            logger.debug("Ignoring unmapped remote key: vk_code=%r keysym=%r", vk_code, keysym)
            return
        eventType = Atspi.KeySynthType.PRESS if pressed else Atspi.KeySynthType.RELEASE
        GLib.idle_add(self._generate_key_event, keyval, eventType)

    def display(self, cells: list[int] | None = None, **kwargs: Any) -> None:
        logger.debug("Remote braille display update ignored until braille routing is implemented: %r", cells)

    def braille_input(self, **kwargs: Any) -> None:
        logger.debug("Remote braille input ignored until braille routing is implemented: %r", kwargs)

    def set_braille_display_size(self, **kwargs: Any) -> None:
        logger.debug("Remote braille size negotiation ignored until braille routing is implemented: %r", kwargs)

    def send_secure_attention_sequence(self, **kwargs: Any) -> None:
        self._presenter("Ctrl Alt Delete is not available on Linux desktops")

    def _generate_key_event(self, keyval: int, eventType: Atspi.KeySynthType) -> bool:
        try:
            Atspi.generate_keyboard_event(keyval, None, eventType)
        except Exception:
            logger.exception("Failed to generate remote key event")
        return False

    def _resolve_keyval(self, vk_code: int | None, keysym: str | None) -> int | None:
        if keysym:
            keyval = Gdk.keyval_from_name(keysym)
            return keyval if keyval else None
        if vk_code is None:
            return None
        mappedKeysym = WINDOWS_VK_TO_ATSPI_KEYSYM.get(int(vk_code))
        if not mappedKeysym:
            return None
        keyval = Gdk.keyval_from_name(mappedKeysym)
        return keyval if keyval else None

    def _speech_sequence_to_text(self, sequence: Any) -> str:
        if sequence is None:
            return ""
        if isinstance(sequence, str):
            return sequence
        if not isinstance(sequence, list):
            return str(sequence)
        parts = []
        for item in sequence:
            if isinstance(item, str):
                parts.append(item)
            elif isinstance(item, list):
                nested = self._speech_sequence_to_text(item)
                if nested:
                    parts.append(nested)
        return " ".join(parts)
