"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PtySession = void 0;
const node_fs_1 = require("node:fs");
const node_pty_1 = require("node-pty");
const TEXT_TO_ENTER_DELAY_MS = 300;
const TRUST_DIALOG_WINDOW_MS = 5000;
const REPL_READY_MIN_BYTES = 100;
const REPL_READY_TIMEOUT_MS = 60_000;
const REPL_SETTLE_MS = 2000;
class PtySession {
    pty = null;
    buffer = "";
    cols;
    rows;
    exited = false;
    exitCode = null;
    opts;
    constructor(opts) {
        this.opts = opts;
        this.cols = opts.cols ?? 160;
        this.rows = opts.rows ?? 50;
    }
    get pid() {
        return this.pty?.pid ?? -1;
    }
    async start() {
        if (this.pty)
            throw new Error("PtySession already started");
        const bin = this.opts.bin ?? "claude";
        const args = ["--verbose", "--settings", this.opts.settingsPath, ...(this.opts.extraArgs ?? [])];
        const env = { ...process.env };
        delete env.CLAUDECODE;
        delete env.CLAUDE_CODE_ENTRYPOINT;
        // Drop any inherited 1M-context override BEFORE applying the caller's env, so a
        // CLAUDE_CODE_DISABLE_1M_CONTEXT sitting in cockpit's own environment can't pin
        // every session to 200k and defeat a 1m pick. The caller sets it per-session
        // via opts.env (200k → "1"; 1m → absent), which is applied on top here.
        delete env.CLAUDE_CODE_DISABLE_1M_CONTEXT;
        Object.assign(env, this.opts.env ?? {});
        const spawnFile = process.platform === "darwin" ? "/bin/zsh" : bin;
        const spawnArgs = process.platform === "darwin" ? ["-l", "-c", `exec ${[bin, ...args].map(shellQuote).join(" ")}`] : args;
        let diagInfo = `platform=${process.platform}, file=${spawnFile}, cwd=${this.opts.cwd}`;
        try {
            const exists = (0, node_fs_1.existsSync)(spawnFile);
            diagInfo += `, exists=${exists}`;
            if (exists) {
                const real = (0, node_fs_1.realpathSync)(spawnFile);
                const stat = (0, node_fs_1.statSync)(real);
                const mode = `0${(stat.mode & 0o7777).toString(8)}`;
                diagInfo += `, realpath=${real}, mode=${mode}, size=${stat.size}`;
            }
        }
        catch (e) {
            diagInfo += `, diagError=${e instanceof Error ? e.message : String(e)}`;
        }
        console.log(`[pty-session] spawn diagnostic: ${diagInfo}`);
        console.log(`[pty-session] spawn args: ${JSON.stringify(spawnArgs).slice(0, 500)}`);
        console.log(`[pty-session] PATH: ${env.PATH?.split(":").slice(0, 10).join(":")}`);
        try {
            this.pty = (0, node_pty_1.spawn)(spawnFile, spawnArgs, {
                name: "xterm-256color",
                cols: this.cols,
                rows: this.rows,
                cwd: this.opts.cwd,
                env,
            });
        }
        catch (err) {
            console.error(`[pty-session] spawn failed: ${err instanceof Error ? err.message : String(err)}`);
            console.error(`[pty-session] full diagnostic: ${diagInfo}`);
            throw err;
        }
        this.pty.onData((data) => {
            this.buffer += data;
            if (this.buffer.length > 64 * 1024)
                this.buffer = this.buffer.slice(-32 * 1024);
            this.opts.onData?.(data);
        });
        this.pty.onExit((info) => {
            this.exited = true;
            this.exitCode = info.exitCode;
            this.opts.onExit?.(info);
            this.pty = null;
        });
        await this.handleTrustDialog();
        await this.waitForReplReady();
    }
    async sendText(text) {
        const pty = this.requirePty();
        pty.write("\x15");
        pty.write(text);
        await sleep(TEXT_TO_ENTER_DELAY_MS);
        pty.write("\r");
    }
    sendSlash(command) {
        const c = command.startsWith("/") ? command : `/${command}`;
        this.requirePty().write(`${c}\r`);
    }
    sendKey(key) {
        this.requirePty().write(key);
    }
    resize(cols, rows) {
        if (!this.pty)
            return;
        if (cols === this.cols && rows === this.rows)
            return;
        this.cols = cols;
        this.rows = rows;
        this.pty.resize(cols, rows);
    }
    kill(signal) {
        if (!this.pty)
            return;
        try {
            this.pty.kill(signal);
        }
        catch {
            // already dead
        }
        this.pty = null;
    }
    requirePty() {
        if (!this.pty)
            throw new Error("PtySession not started or already exited");
        return this.pty;
    }
    cleanOutput() {
        // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
        const ansi = /\x1b\[[0-9;]*[a-zA-Z]/g;
        // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI extras
        const extras = /\x1b[>=?][^\x1b]*/g;
        return this.buffer.replace(ansi, "").replace(extras, "");
    }
    async handleTrustDialog() {
        const deadline = Date.now() + TRUST_DIALOG_WINDOW_MS;
        let accepted = false;
        while (Date.now() < deadline) {
            if (this.exited)
                return;
            const clean = this.cleanOutput();
            if (clean.includes("trust") || clean.includes("Yes,")) {
                this.requirePty().write("\r");
                accepted = true;
                break;
            }
            if (clean.length > REPL_READY_MIN_BYTES)
                break;
            await sleep(200);
        }
        if (accepted)
            await sleep(2000);
    }
    async waitForReplReady() {
        const deadline = Date.now() + REPL_READY_TIMEOUT_MS;
        while (Date.now() < deadline) {
            if (this.exited) {
                throw new Error(`claude exited during startup (code=${this.exitCode}, output=${this.cleanOutput().slice(0, 200)})`);
            }
            if (this.cleanOutput().length >= REPL_READY_MIN_BYTES) {
                await sleep(REPL_SETTLE_MS);
                if (this.exited) {
                    throw new Error(`claude exited during startup (code=${this.exitCode}, output=${this.cleanOutput().slice(0, 200)})`);
                }
                return;
            }
            await sleep(200);
        }
        const clean = this.cleanOutput();
        throw new Error(`Timed out after ${REPL_READY_TIMEOUT_MS}ms waiting for claude REPL (got ${clean.length} bytes: ${clean.slice(0, 200)})`);
    }
}
exports.PtySession = PtySession;
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
function shellQuote(s) {
    if (!/[\s"'\\$`!#&|;()<>]/.test(s))
        return s;
    return `'${s.replace(/'/g, "'\\''")}'`;
}
