from __future__ import annotations

import io
import os
import sys
from dataclasses import dataclass

from .interactive_profiles import SECOND_PORT_FALLBACK_DIGITS

CONTROL_KEYS = {
    " ": "repeat_tick",
    "r": "reset",
    "s": "summary",
    "t": "toggle_output",
    "c": "clear",
    "q": "quit",
    "h": "help",
}

KEYPAD_TOKEN_TO_DIGIT = {
    "KP0": 0,
    "KP1": 1,
    "KP2": 2,
    "KP3": 3,
    "KP4": 4,
    "KP5": 5,
    "KP6": 6,
    "KP7": 7,
    "KP8": 8,
    "KP9": 9,
}

POSIX_KEYPAD_ESCAPES = {
    "\x1bOp": "KP0",
    "\x1bOq": "KP1",
    "\x1bOr": "KP2",
    "\x1bOs": "KP3",
    "\x1bOt": "KP4",
    "\x1bOu": "KP5",
    "\x1bOv": "KP6",
    "\x1bOw": "KP7",
    "\x1bOx": "KP8",
    "\x1bOy": "KP9",
}


@dataclass(frozen=True)
class InteractiveCommand:
    action: str
    raw_key: str
    updated_port: str | None = None
    digit_value: int | None = None


def map_digit_to_value(digit_value: int, normalize_mode: str) -> float:
    bounded_digit = max(0, min(9, int(digit_value)))
    normalized_mode = str(normalize_mode or "normalized").strip().lower()
    if normalized_mode == "raw":
        return float(bounded_digit)
    return bounded_digit / 10.0


def interpret_key(raw_key: str, input_ports: list[str]) -> InteractiveCommand:
    if raw_key in KEYPAD_TOKEN_TO_DIGIT and len(input_ports) >= 2:
        return InteractiveCommand(
            action="input_tick",
            raw_key=raw_key,
            updated_port=input_ports[1],
            digit_value=KEYPAD_TOKEN_TO_DIGIT[raw_key],
        )

    if len(input_ports) >= 2 and raw_key in SECOND_PORT_FALLBACK_DIGITS:
        return InteractiveCommand(
            action="input_tick",
            raw_key=raw_key,
            updated_port=input_ports[1],
            digit_value=SECOND_PORT_FALLBACK_DIGITS[raw_key],
        )

    normalized_key = str(raw_key or "").lower()
    if normalized_key in CONTROL_KEYS:
        return InteractiveCommand(action=CONTROL_KEYS[normalized_key], raw_key=raw_key)

    if normalized_key.isdigit():
        return InteractiveCommand(
            action="input_tick",
            raw_key=raw_key,
            updated_port=input_ports[0] if input_ports else None,
            digit_value=int(normalized_key),
        )

    return InteractiveCommand(action="noop", raw_key=raw_key)


def second_port_fallback_description() -> str:
    return "Q W E R T Y U I O P -> 1 2 3 4 5 6 7 8 9 0"


def read_keypress() -> str:
    if os.name == "nt":
        return _read_windows_keypress()
    return _read_posix_keypress()


def _read_windows_keypress() -> str:
    import msvcrt

    first_char = msvcrt.getwch()
    if first_char in ("\x00", "\xe0"):
        extended_char = msvcrt.getwch()
        return _map_windows_extended_key(extended_char)
    return first_char


def _map_windows_extended_key(extended_char: str) -> str:
    windows_special_map = {
        "H": "UP",
        "P": "DOWN",
        "K": "LEFT",
        "M": "RIGHT",
    }
    return windows_special_map.get(extended_char, "")


def _read_posix_keypress() -> str:
    import select
    import termios
    import tty

    input_stream = sys.stdin
    if not isinstance(input_stream, io.TextIOBase):
        return ""

    file_descriptor = input_stream.fileno()
    previous_settings = termios.tcgetattr(file_descriptor)
    try:
        tty.setraw(file_descriptor)
        first_char = input_stream.read(1)
        if first_char != "\x1b":
            return first_char

        escape_sequence = first_char
        while True:
            ready_streams, _, _ = select.select([input_stream], [], [], 0.01)
            if not ready_streams:
                break
            escape_sequence += input_stream.read(1)
            if escape_sequence in POSIX_KEYPAD_ESCAPES:
                return POSIX_KEYPAD_ESCAPES[escape_sequence]
            if len(escape_sequence) >= 3 and escape_sequence not in POSIX_KEYPAD_ESCAPES:
                break
        return escape_sequence
    finally:
        termios.tcsetattr(file_descriptor, termios.TCSADRAIN, previous_settings)
