import { closeSync, openSync } from "node:fs";
import { ReadStream } from "node:tty";
import { ProcessTerminal, StdinBuffer, setKittyProtocolActive, } from "@earendil-works/pi-tui";
export class TauTerminal {
    input;
    output;
    cleanupInput;
    wasRaw = false;
    inputHandler;
    resizeHandler;
    _kittyProtocolActive = false;
    stdinBuffer;
    stdinDataHandler;
    constructor(input, output, cleanupInput) {
        this.input = input;
        this.output = output;
        this.cleanupInput = cleanupInput;
    }
    start(onInput, onResize) {
        this.inputHandler = onInput;
        this.resizeHandler = onResize;
        this.wasRaw = this.input.isRaw || false;
        if (this.input.setRawMode) {
            this.input.setRawMode(true);
        }
        this.input.setEncoding("utf8");
        this.input.resume();
        this.output.write("\x1b[?2004h");
        this.output.on("resize", this.resizeHandler);
        if (process.platform !== "win32") {
            process.kill(process.pid, "SIGWINCH");
        }
        this.queryAndEnableKittyProtocol();
    }
    setupStdinBuffer() {
        this.stdinBuffer = new StdinBuffer({ timeout: 10 });
        // biome-ignore lint/suspicious/noControlCharactersInRegex: kitty keyboard response is an ESC sequence
        const kittyResponsePattern = /^\x1b\[\?(\d+)u$/;
        this.stdinBuffer.on("data", (sequence) => {
            if (!this._kittyProtocolActive) {
                const match = sequence.match(kittyResponsePattern);
                if (match) {
                    this._kittyProtocolActive = true;
                    setKittyProtocolActive(true);
                    this.output.write("\x1b[>7u");
                    return;
                }
            }
            if (this.inputHandler) {
                this.inputHandler(sequence);
            }
        });
        this.stdinBuffer.on("paste", (content) => {
            if (this.inputHandler) {
                this.inputHandler(`\x1b[200~${content}\x1b[201~`);
            }
        });
        this.stdinDataHandler = (data) => {
            this.stdinBuffer.process(data);
        };
    }
    queryAndEnableKittyProtocol() {
        this.setupStdinBuffer();
        this.input.on("data", this.stdinDataHandler);
        this.output.write("\x1b[?u");
    }
    async drainInput(maxMs = 1000, idleMs = 50) {
        if (this._kittyProtocolActive) {
            this.output.write("\x1b[<u");
            this._kittyProtocolActive = false;
            setKittyProtocolActive(false);
        }
        const previousHandler = this.inputHandler;
        this.inputHandler = undefined;
        let lastDataTime = Date.now();
        const onData = () => {
            lastDataTime = Date.now();
        };
        this.input.on("data", onData);
        const endTime = Date.now() + maxMs;
        try {
            while (true) {
                const now = Date.now();
                const timeLeft = endTime - now;
                if (timeLeft <= 0)
                    break;
                if (now - lastDataTime >= idleMs)
                    break;
                await new Promise((resolve) => setTimeout(resolve, Math.min(idleMs, timeLeft)));
            }
        }
        finally {
            this.input.removeListener("data", onData);
            this.inputHandler = previousHandler;
        }
    }
    stop() {
        this.output.write("\x1b[?2004l");
        if (this._kittyProtocolActive) {
            this.output.write("\x1b[<u");
            this._kittyProtocolActive = false;
            setKittyProtocolActive(false);
        }
        if (this.stdinBuffer) {
            this.stdinBuffer.destroy();
            this.stdinBuffer = undefined;
        }
        if (this.stdinDataHandler) {
            this.input.removeListener("data", this.stdinDataHandler);
            this.stdinDataHandler = undefined;
        }
        this.inputHandler = undefined;
        if (this.resizeHandler) {
            this.output.removeListener("resize", this.resizeHandler);
            this.resizeHandler = undefined;
        }
        this.input.pause();
        if (this.input.setRawMode) {
            this.input.setRawMode(this.wasRaw);
        }
        if (this.cleanupInput) {
            this.cleanupInput();
            this.cleanupInput = undefined;
        }
    }
    write(data) {
        this.output.write(data);
    }
    get columns() {
        return this.output.columns || 80;
    }
    get rows() {
        return this.output.rows || 24;
    }
    get kittyProtocolActive() {
        return this._kittyProtocolActive;
    }
    moveBy(lines) {
        if (lines > 0) {
            this.output.write(`\x1b[${lines}B`);
        }
        else if (lines < 0) {
            this.output.write(`\x1b[${-lines}A`);
        }
    }
    hideCursor() {
        this.output.write("\x1b[?25l");
    }
    showCursor() {
        this.output.write("\x1b[?25h");
    }
    clearLine() {
        this.output.write("\x1b[K");
    }
    clearFromCursor() {
        this.output.write("\x1b[J");
    }
    clearScreen() {
        this.output.write("\x1b[2J\x1b[H");
    }
    setTitle(title) {
        const safeTitle = Array.from(title)
            .filter((char) => {
            const code = char.codePointAt(0);
            return code !== undefined && !(code <= 0x1f || code === 0x7f);
        })
            .join("");
        this.output.write(`\x1b]0;${safeTitle}\x07`);
    }
    setProgress(active) {
        if (active) {
            this.output.write("\x1b]9;4;3\x07");
        }
        else {
            this.output.write("\x1b]9;4;0;\x07");
        }
    }
}
/**
 * Create a terminal for Tau. If stdin is piped and we're still running in a TTY,
 * we fall back to reading input from /dev/tty so the chat remains interactive.
 */
export function createAppTerminal() {
    // Normal mode: stdin is a TTY, use upstream ProcessTerminal.
    if (process.stdin.isTTY) {
        return new ProcessTerminal();
    }
    // If stdin is not a TTY but we're still in an interactive terminal,
    // try to read input from /dev/tty so the UI stays interactive.
    if (process.stdout.isTTY) {
        try {
            const fd = openSync("/dev/tty", "r");
            const input = new ReadStream(fd);
            return new TauTerminal(input, process.stdout, () => {
                input.destroy();
                try {
                    closeSync(fd);
                }
                catch {
                    // ignore
                }
            });
        }
        catch {
            // Fall through
        }
    }
    return new ProcessTerminal();
}
//# sourceMappingURL=terminal.js.map