#!/usr/bin/env python3
import json
import os
import pty
import select
import sys


def emit(event_type, **payload):
    print(json.dumps({"type": event_type, **payload}), flush=True)


def handle_message(pid, fd, raw_line):
    try:
        message = json.loads(raw_line.decode("utf-8", "replace"))
    except json.JSONDecodeError:
        return

    if message.get("type") == "input":
        os.write(fd, str(message.get("data", "")).encode("utf-8", "replace"))
    elif message.get("type") == "signal":
        os.kill(pid, int(message.get("signal", 15)))


def main():
    if "--" not in sys.argv:
        emit("exit", code=2, signaled=False)
        return 2

    command = sys.argv[sys.argv.index("--") + 1 :]
    if not command:
        emit("exit", code=2, signaled=False)
        return 2

    pid, fd = pty.fork()
    if pid == 0:
        os.execvp(command[0], command)

    emit("start", pid=pid)
    stdin_fd = sys.stdin.fileno()
    open_fds = [fd, stdin_fd]
    stdin_buffer = b""

    while open_fds:
        readable, _, _ = select.select(open_fds, [], [], 0.2)

        if fd in readable:
            try:
                data = os.read(fd, 4096)
            except OSError:
                data = b""

            if data:
                emit("out", data=data.decode("utf-8", "replace"))
            else:
                open_fds.remove(fd)

        if stdin_fd in readable:
            try:
                chunk = os.read(stdin_fd, 4096)
            except OSError:
                chunk = b""

            if not chunk:
                open_fds.remove(stdin_fd)
                continue

            stdin_buffer += chunk
            while b"\n" in stdin_buffer:
                line, stdin_buffer = stdin_buffer.split(b"\n", 1)
                handle_message(pid, fd, line)

        try:
            finished_pid, status = os.waitpid(pid, os.WNOHANG)
        except ChildProcessError:
            break

        if finished_pid == pid:
            if os.WIFSIGNALED(status):
                emit("exit", code=os.WTERMSIG(status), signaled=True)
                return 128 + os.WTERMSIG(status)

            code = os.WEXITSTATUS(status)
            emit("exit", code=code, signaled=False)
            return code

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
