import os
import selectors
import sys
import threading


class InterruptibleInput:
    """
    Unix-only, interruptible replacement for input(), designed for:
      await asyncio.get_event_loop().run_in_executor(None, obj.input, prompt)

    interrupt() is safe from any thread.
    """

    def __init__(self):
        if os.name == "nt":
            raise RuntimeError("InterruptibleInput is Unix-only (requires selectable stdin).")

        self._cancel = threading.Event()
        self._sel = selectors.DefaultSelector()

        # self-pipe to wake up select() from interrupt()
        self._r, self._w = os.pipe()
        os.set_blocking(self._r, False)
        os.set_blocking(self._w, False)
        self._sel.register(self._r, selectors.EVENT_READ, data="__wakeup__")

    def close(self) -> None:
        try:
            self._sel.unregister(self._r)
        except Exception:
            pass
        try:
            os.close(self._r)
        except Exception:
            pass
        try:
            os.close(self._w)
        except Exception:
            pass
        try:
            self._sel.close()
        except Exception:
            pass

    def interrupt(self) -> None:
        self._cancel.set()
        try:
            os.write(self._w, b"\x01")  # wake selector immediately
        except BlockingIOError:
            pass
        except OSError:
            pass

    def input(self, prompt: str = "") -> str:
        if prompt:
            sys.stdout.write(prompt)
            sys.stdout.flush()

        if self._cancel.is_set():
            self._cancel.clear()
            raise InterruptedError("Input interrupted")

        stdin = sys.stdin
        fd = stdin.fileno()

        self._sel.register(fd, selectors.EVENT_READ, data="__stdin__")
        try:
            while True:
                for key, _ in self._sel.select():
                    if key.data == "__wakeup__":
                        # drain wake bytes
                        try:
                            while os.read(self._r, 1024):
                                pass
                        except BlockingIOError:
                            pass

                        if self._cancel.is_set():
                            self._cancel.clear()
                            raise InterruptedError("Input interrupted")
                        continue

                    if key.data == "__stdin__":
                        line = stdin.readline()
                        if line == "":
                            raise EOFError
                        return line.rstrip("\n")
        finally:
            try:
                self._sel.unregister(fd)
            except Exception:
                pass
