/**
 * Shared child-process lifecycle management.
 *
 * Handles timeout, abort, stream piping, error/close events, and
 * the settle flag that prevents double-resolution. Used by both
 * local bash (createDefaultBashOperations) and sandbox executor.
 */
import { killProcessTree } from './tree.js';
/**
 * Manage a spawned child process through its full lifecycle.
 * Returns when the process exits (or is killed by timeout/abort).
 */
export function manageChildProcess(child, { onData, signal, timeout }) {
    return new Promise((resolve, reject) => {
        let timedOut = false;
        let settled = false;
        let forcedSettleHandle;
        const clearHandles = () => {
            if (timeoutHandle)
                clearTimeout(timeoutHandle);
            if (forcedSettleHandle)
                clearTimeout(forcedSettleHandle);
        };
        // Timeout handling
        let timeoutHandle;
        if (timeout !== undefined && timeout > 0) {
            timeoutHandle = setTimeout(() => {
                timedOut = true;
                if (child.pid) {
                    killProcessTree(child.pid);
                }
                // Force-close pipes — on Windows, grandchild processes can hold
                // pipe handles open even after the shell is killed.
                child.stdout?.destroy();
                child.stderr?.destroy();
                // Safety net: if 'close' never fires after timeout cleanup, force-settle
                // so the tool call doesn't hang waiting on stale pipe handles.
                forcedSettleHandle = setTimeout(() => {
                    if (!settled) {
                        settled = true;
                        clearHandles();
                        if (signal)
                            signal.removeEventListener('abort', onAbort);
                        reject(new Error(`timeout:${timeout}`));
                    }
                }, 2000);
            }, timeout * 1000);
        }
        // Stream stdout and stderr
        if (child.stdout)
            child.stdout.on('data', onData);
        if (child.stderr)
            child.stderr.on('data', onData);
        // Spawn errors
        child.on('error', (err) => {
            clearHandles();
            if (signal)
                signal.removeEventListener('abort', onAbort);
            if (!settled) {
                settled = true;
                reject(err);
            }
        });
        // Abort handling — kill process tree and force-close pipes.
        const onAbort = () => {
            if (child.pid) {
                killProcessTree(child.pid);
            }
            child.stdout?.destroy();
            child.stderr?.destroy();
            // Safety net: if 'close' still doesn't fire (orphaned pipe handles),
            // force-settle after 2s so the tool call doesn't hang forever.
            setTimeout(() => {
                if (!settled) {
                    settled = true;
                    clearHandles();
                    if (signal)
                        signal.removeEventListener('abort', onAbort);
                    reject(new Error('aborted'));
                }
            }, 2000);
        };
        if (signal) {
            if (signal.aborted) {
                onAbort();
            }
            else {
                signal.addEventListener('abort', onAbort, { once: true });
            }
        }
        // Process exit
        child.on('close', (code) => {
            clearHandles();
            if (signal)
                signal.removeEventListener('abort', onAbort);
            if (settled)
                return;
            settled = true;
            if (signal?.aborted) {
                reject(new Error('aborted'));
                return;
            }
            if (timedOut) {
                reject(new Error(`timeout:${timeout}`));
                return;
            }
            resolve({ exitCode: code });
        });
    });
}
//# sourceMappingURL=lifecycle.js.map