from __future__ import annotations

import re
import sys
import time


_ARROW_MAP = {
    '\x1b[A': ':up',
    '\x1b[B': ':down',
    '\x1b[C': ':right',
    '\x1b[D': ':left',
}


def _enable_windows_vt() -> None:
    if not sys.platform.startswith('win'):
        return
    try:
        import ctypes
        kernel32 = ctypes.windll.kernel32
        STD_INPUT_HANDLE = -10
        STD_OUTPUT_HANDLE = -11
        ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
        ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
        for handle_id, flag in ((STD_INPUT_HANDLE, ENABLE_VIRTUAL_TERMINAL_INPUT), (STD_OUTPUT_HANDLE, ENABLE_VIRTUAL_TERMINAL_PROCESSING)):
            handle = kernel32.GetStdHandle(handle_id)
            mode = ctypes.c_uint()
            if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
                kernel32.SetConsoleMode(handle, mode.value | flag)
    except Exception:
        return


def enable_mouse() -> None:
    if not sys.stdout.isatty():
        return
    _enable_windows_vt()
    print('\x1b[?1000h\x1b[?1002h\x1b[?1006h', end='', flush=True)


def disable_mouse() -> None:
    if not sys.stdout.isatty():
        return
    print('\x1b[?1000l\x1b[?1002l\x1b[?1006l', end='', flush=True)


def _parse_mouse(seq: str) -> str | None:
    match = re.match(r'\x1b\[<(?P<button>\d+);(?P<x>\d+);(?P<y>\d+)(?P<kind>[Mm])', seq)
    if match:
        button = int(match.group('button'))
        x = int(match.group('x'))
        y = int(match.group('y'))
        kind = match.group('kind')
        if kind == 'M':
            return f':click:{x}:{y}:{button}'
        return f':release:{x}:{y}:{button}'
    if seq.startswith('\x1b[M') and len(seq) >= 6:
        button = ord(seq[3]) - 32
        x = ord(seq[4]) - 32
        y = ord(seq[5]) - 32
        return f':click:{x}:{y}:{button}'
    return None


def _parse_escape_sequence(seq: str) -> str | None:
    if seq in _ARROW_MAP:
        return _ARROW_MAP[seq]
    mouse = _parse_mouse(seq)
    if mouse:
        return mouse
    return None


def read_command(prompt: str = '') -> str:
    if not sys.stdin.isatty():
        try:
            return input(prompt)
        except EOFError:
            return 'q'
    try:
        if sys.platform.startswith('win'):
            return _read_windows(prompt)
        return _read_unix(prompt)
    except Exception:
        try:
            return input(prompt)
        except EOFError:
            return 'q'


def _collect_windows_escape(msvcrt_module) -> str:
    seq = '\x1b'
    start = time.time()
    while time.time() - start < 0.12:
        if not msvcrt_module.kbhit():
            time.sleep(0.005)
            continue
        ch = msvcrt_module.getwch()
        seq += ch
        if ch in 'ABCDMm~' or (seq.startswith('\x1b[<') and ch in 'Mm'):
            break
    return seq


def _read_windows(prompt: str) -> str:
    import msvcrt

    _enable_windows_vt()
    print(prompt, end='', flush=True)
    chars: list[str] = []
    while True:
        ch = msvcrt.getwch()
        if ch in ('\r', '\n'):
            print()
            return ''.join(chars)
        if ch == '\x03':
            raise KeyboardInterrupt
        if ch in ('\x00', '\xe0'):
            code = msvcrt.getwch()
            mapping = {'H': ':up', 'P': ':down', 'K': ':left', 'M': ':right'}
            if not chars and code in mapping:
                print()
                return mapping[code]
            continue
        if ch == '\x1b':
            seq = _collect_windows_escape(msvcrt)
            parsed = _parse_escape_sequence(seq)
            if parsed and not chars:
                print()
                return parsed
            continue
        if ch == '\x08':
            if chars:
                chars.pop()
                print('\b \b', end='', flush=True)
            continue
        chars.append(ch)
        print(ch, end='', flush=True)


def _read_unix(prompt: str) -> str:
    import termios
    import tty

    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    print(prompt, end='', flush=True)
    chars: list[str] = []
    try:
        tty.setraw(fd)
        while True:
            ch = sys.stdin.read(1)
            if ch in ('\r', '\n'):
                print()
                return ''.join(chars)
            if ch == '\x03':
                raise KeyboardInterrupt
            if ch == '\x7f':
                if chars:
                    chars.pop()
                    print('\b \b', end='', flush=True)
                continue
            if ch == '\x1b':
                seq = ch
                next_ch = sys.stdin.read(1)
                seq += next_ch
                if next_ch == '[':
                    while True:
                        tail = sys.stdin.read(1)
                        seq += tail
                        if tail.isalpha() or tail in '~Mm':
                            break
                parsed = _parse_escape_sequence(seq)
                if parsed and not chars:
                    print()
                    return parsed
                continue
            chars.append(ch)
            print(ch, end='', flush=True)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old)
