"""Terminal input with up/down arrow command history."""

from __future__ import annotations

import sys
import tty
import termios


class InputHistory:
    """Reads a line of input with up/down arrow history navigation.

    History buffer grows dynamically with no fixed limit.
    """

    def __init__(self) -> None:
        self._history: list[str] = []

    def input(self, prompt: str = "> ") -> str:
        """Read a line with history support. Raises EOFError on Ctrl-D."""
        sys.stdout.write(prompt)
        sys.stdout.flush()

        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            line = self._read_line(fd, prompt)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

        sys.stdout.write("\n")
        sys.stdout.flush()

        if line:
            self._history.append(line)
        return line

    def _read_line(self, fd: int, prompt: str) -> str:
        buf: list[str] = []
        cursor = 0
        # Index into history; len(history) means "current new line"
        hist_idx = len(self._history)
        saved_line = ""  # what the user typed before browsing history

        while True:
            ch = self._read_char(fd)

            if ch == "\r" or ch == "\n":
                return "".join(buf)

            if ch == "\x04":  # Ctrl-D
                if not buf:
                    raise EOFError
                continue

            if ch == "\x03":  # Ctrl-C
                raise KeyboardInterrupt

            if ch == "\x7f" or ch == "\x08":  # Backspace
                if cursor > 0:
                    buf.pop(cursor - 1)
                    cursor -= 1
                    self._redraw(buf, cursor, prompt)
                continue

            if ch == "\x1b":  # Escape sequence
                seq1 = self._read_char(fd)
                if seq1 == "[":
                    seq2 = self._read_char(fd)
                    if seq2 == "A":  # Up arrow
                        if hist_idx > 0:
                            if hist_idx == len(self._history):
                                saved_line = "".join(buf)
                            hist_idx -= 1
                            buf = list(self._history[hist_idx])
                            cursor = len(buf)
                            self._redraw(buf, cursor, prompt)
                        continue
                    if seq2 == "B":  # Down arrow
                        if hist_idx < len(self._history):
                            hist_idx += 1
                            if hist_idx == len(self._history):
                                buf = list(saved_line)
                            else:
                                buf = list(self._history[hist_idx])
                            cursor = len(buf)
                            self._redraw(buf, cursor, prompt)
                        continue
                    if seq2 == "C":  # Right arrow
                        if cursor < len(buf):
                            cursor += 1
                            sys.stdout.write("\x1b[C")
                            sys.stdout.flush()
                        continue
                    if seq2 == "D":  # Left arrow
                        if cursor > 0:
                            cursor -= 1
                            sys.stdout.write("\x1b[D")
                            sys.stdout.flush()
                        continue
                    if seq2 == "3":  # Possible Delete key
                        seq3 = self._read_char(fd)
                        if seq3 == "~" and cursor < len(buf):
                            buf.pop(cursor)
                            self._redraw(buf, cursor, prompt)
                        continue
                    if seq2 == "H":  # Home
                        cursor = 0
                        self._redraw(buf, cursor, prompt)
                        continue
                    if seq2 == "F":  # End
                        cursor = len(buf)
                        self._redraw(buf, cursor, prompt)
                        continue
                continue

            # Regular printable character
            if ch.isprintable():
                buf.insert(cursor, ch)
                cursor += 1
                if cursor == len(buf):
                    sys.stdout.write(ch)
                    sys.stdout.flush()
                else:
                    self._redraw(buf, cursor, prompt)

        return "".join(buf)  # unreachable, keeps type-checker happy

    def _read_char(self, fd: int) -> str:
        return sys.stdin.read(1)

    @staticmethod
    def _redraw(buf: list[str], cursor: int, prompt: str) -> None:
        """Clear the line and redraw the prompt + buffer with cursor positioned."""
        line = "".join(buf)
        # Move to start of line, clear it, rewrite
        sys.stdout.write(f"\r\x1b[K{prompt}{line}")
        # Position cursor
        back = len(line) - cursor
        if back > 0:
            sys.stdout.write(f"\x1b[{back}D")
        sys.stdout.flush()
