import * as pty from 'node-pty';
import { chmodSync, existsSync, statSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { detectShell, shellArgs } from './shell-detect.js';
const MAX_HISTORY_BYTES = 200_000;
const require = createRequire(import.meta.url);
const MUX_ENV_NAMES = new Set(['TMUX', 'TMUX_PANE', 'STY', 'WINDOW']);
function send(ws, msg) {
    if (ws.readyState === 1) {
        ws.send(JSON.stringify(msg));
    }
}
function broadcast(runtime, msg) {
    for (const ws of runtime.subscribers) {
        send(ws, msg);
    }
}
function ensureNodePtyHelperExecutable() {
    if (process.platform !== 'darwin' && process.platform !== 'linux')
        return;
    try {
        const entry = require.resolve('node-pty');
        const packageRoot = dirname(dirname(entry));
        const helper = join(packageRoot, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper');
        if (!existsSync(helper))
            return;
        const current = statSync(helper).mode;
        if ((current & 0o111) === 0) {
            chmodSync(helper, current | 0o755);
        }
    }
    catch {
        // If the helper cannot be inspected, pty.spawn will surface the underlying failure.
    }
}
function shouldDropEnv(name) {
    return MUX_ENV_NAMES.has(name) || name.startsWith('BYOBU_');
}
export function terminalEnv(shell, baseEnv = process.env) {
    const env = {};
    for (const [name, value] of Object.entries(baseEnv)) {
        if (value === undefined || shouldDropEnv(name))
            continue;
        env[name] = value;
    }
    env.SHELL = shell;
    env.TERM = 'xterm-256color';
    env.COLORTERM = 'truecolor';
    env.TERM_PROGRAM = 'fatmux';
    env.BYOBU_DISABLE = '1';
    env.LC_BYOBU = '0';
    return env;
}
export class PtyManager {
    db;
    runtimes = new Map();
    disposing = false;
    constructor(db) {
        this.db = db;
    }
    list(projectId) {
        return this.db.listTerminals(projectId).filter((terminal) => terminal.alive);
    }
    create(input) {
        const count = this.activeCount(input.projectId);
        const name = input.name?.trim() || `Shell ${count + 1}`;
        const shell = detectShell();
        ensureNodePtyHelperExecutable();
        const proc = pty.spawn(shell, shellArgs(shell), {
            name: 'xterm-256color',
            cols: Math.max(input.cols, 10),
            rows: Math.max(input.rows, 2),
            cwd: input.projectPath,
            env: terminalEnv(shell)
        });
        const session = this.db.createTerminal({
            projectId: input.projectId,
            name
        });
        const runtime = {
            session,
            process: proc,
            history: '',
            subscribers: new Set()
        };
        proc.onData((data) => {
            runtime.history += data;
            if (runtime.history.length > MAX_HISTORY_BYTES) {
                runtime.history = runtime.history.slice(-MAX_HISTORY_BYTES);
            }
            this.db.updateTerminalHistory(session.id, runtime.history);
            broadcast(runtime, {
                type: 'terminal.output',
                terminalId: session.id,
                data,
                replay: false
            });
        });
        proc.onExit(({ exitCode }) => {
            if (this.disposing)
                return;
            const updated = this.db.markTerminalExited(session.id, exitCode);
            if (updated) {
                runtime.session = updated;
                broadcast(runtime, { type: 'terminal.status', session: updated });
            }
            this.runtimes.delete(session.id);
        });
        this.runtimes.set(session.id, runtime);
        return session;
    }
    attach(terminalId, ws) {
        const runtime = this.runtimes.get(terminalId);
        const persisted = this.db.getTerminal(terminalId);
        if (!persisted)
            return null;
        if (runtime) {
            runtime.subscribers.add(ws);
            send(ws, { type: 'terminal.status', session: runtime.session });
            if (runtime.history) {
                send(ws, {
                    type: 'terminal.output',
                    terminalId,
                    data: runtime.history,
                    replay: true
                });
            }
            return runtime.session;
        }
        send(ws, { type: 'terminal.status', session: persisted });
        if (persisted.history) {
            send(ws, {
                type: 'terminal.output',
                terminalId,
                data: persisted.history,
                replay: true
            });
        }
        return persisted;
    }
    input(terminalId, data) {
        const runtime = this.runtimes.get(terminalId);
        runtime?.process.write(data);
    }
    resize(terminalId, cols, rows) {
        const runtime = this.runtimes.get(terminalId);
        runtime?.process.resize(Math.max(cols, 10), Math.max(rows, 2));
    }
    close(terminalId) {
        const runtime = this.runtimes.get(terminalId);
        if (runtime) {
            runtime.process.kill();
            this.runtimes.delete(terminalId);
        }
        return this.db.markTerminalExited(terminalId, null);
    }
    unsubscribeAll(ws) {
        for (const runtime of this.runtimes.values()) {
            runtime.subscribers.delete(ws);
        }
    }
    activeCount(projectId) {
        return this.db.listTerminals(projectId).filter((terminal) => terminal.alive).length;
    }
    disposeAll() {
        this.disposing = true;
        for (const runtime of this.runtimes.values()) {
            runtime.process.kill();
        }
        this.runtimes.clear();
    }
}
//# sourceMappingURL=pty-manager.js.map