/**
 * PTY Terminal Session Service
 *
 * `node-pty`-backed implementation of `ITerminalSessionService`. Spawns a
 * real pseudo-terminal so interactive tools (vim, top, git, pnpm, etc.)
 * work exactly as they would in a native terminal. Cross-platform via
 * ConPTY on Windows and forkpty on Unix.
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { injectable } from 'tsyringe';
import { randomUUID } from 'node:crypto';
import { createRequire } from 'node:module';
import { IS_WINDOWS } from '../../platform.js';
const MAX_SCROLLBACK_BYTES = 256 * 1024; // 256 KB per session
const requireCjs = createRequire(import.meta.url);
let PtyTerminalSessionService = class PtyTerminalSessionService {
    sessions = new Map();
    ptyModule = null;
    loadPty() {
        if (this.ptyModule)
            return this.ptyModule;
        // Dynamic CJS require so the native module is only loaded on demand.
        const mod = requireCjs('node-pty');
        this.ptyModule = mod;
        return mod;
    }
    resolveShell() {
        if (IS_WINDOWS) {
            const shell = process.env.COMSPEC ?? 'powershell.exe';
            return { shell, args: [] };
        }
        const shell = process.env.SHELL ?? '/bin/bash';
        return { shell, args: [] };
    }
    create(input) {
        const pty = this.loadPty();
        const { shell, args } = this.resolveShell();
        const cols = input.cols && input.cols > 0 ? input.cols : 80;
        const rows = input.rows && input.rows > 0 ? input.rows : 24;
        const proc = pty.spawn(shell, args, {
            name: 'xterm-256color',
            cols,
            rows,
            cwd: input.cwd,
            env: {
                ...process.env,
                TERM: 'xterm-256color',
                COLORTERM: 'truecolor',
                FORCE_COLOR: '1',
                ...(input.extraEnv ?? {}),
            },
        });
        const id = randomUUID();
        const entry = {
            id,
            pty: proc,
            shell,
            cwd: input.cwd,
            scrollback: [],
            scrollbackBytes: 0,
            dataListeners: new Set(),
            exitListeners: new Set(),
            exited: false,
            exitCode: null,
        };
        this.sessions.set(id, entry);
        proc.onData((chunk) => {
            // Maintain bounded scrollback so reconnecting clients see recent state.
            entry.scrollback.push(chunk);
            entry.scrollbackBytes += chunk.length;
            while (entry.scrollbackBytes > MAX_SCROLLBACK_BYTES && entry.scrollback.length > 0) {
                const dropped = entry.scrollback.shift();
                if (dropped)
                    entry.scrollbackBytes -= dropped.length;
            }
            for (const listener of entry.dataListeners) {
                try {
                    listener(chunk);
                }
                catch {
                    // Ignore listener errors so one bad subscriber can't poison others.
                }
            }
        });
        proc.onExit(({ exitCode }) => {
            entry.exited = true;
            entry.exitCode = exitCode;
            for (const listener of entry.exitListeners) {
                try {
                    listener(exitCode);
                }
                catch {
                    // ignore
                }
            }
            // Keep entry briefly so late SSE unsubscribes still work, then drop.
            setTimeout(() => {
                this.sessions.delete(id);
            }, 5_000);
        });
        return { id, shell, cwd: input.cwd };
    }
    write(sessionId, data) {
        const entry = this.sessions.get(sessionId);
        if (!entry || entry.exited)
            return;
        entry.pty.write(data);
    }
    resize(sessionId, cols, rows) {
        const entry = this.sessions.get(sessionId);
        if (!entry || entry.exited)
            return;
        if (cols > 0 && rows > 0) {
            try {
                entry.pty.resize(cols, rows);
            }
            catch {
                // resize can throw if the child already exited — swallow
            }
        }
    }
    subscribe(sessionId, onData, onExit) {
        const entry = this.sessions.get(sessionId);
        if (!entry) {
            return () => {
                /* no-op for missing session */
            };
        }
        // Prime new subscriber with scrollback so reconnects see history.
        if (entry.scrollback.length > 0) {
            try {
                onData(entry.scrollback.join(''));
            }
            catch {
                // ignore
            }
        }
        entry.dataListeners.add(onData);
        if (onExit)
            entry.exitListeners.add(onExit);
        // If the session already exited, fire exit listener on next tick.
        if (entry.exited && onExit) {
            const code = entry.exitCode;
            setImmediate(() => {
                try {
                    onExit(code);
                }
                catch {
                    // ignore
                }
            });
        }
        return () => {
            entry.dataListeners.delete(onData);
            if (onExit)
                entry.exitListeners.delete(onExit);
        };
    }
    exists(sessionId) {
        return this.sessions.has(sessionId);
    }
    close(sessionId) {
        const entry = this.sessions.get(sessionId);
        if (!entry)
            return;
        try {
            entry.pty.kill();
        }
        catch {
            // ignore
        }
        this.sessions.delete(sessionId);
    }
};
PtyTerminalSessionService = __decorate([
    injectable()
], PtyTerminalSessionService);
export { PtyTerminalSessionService };
