import { existsSync, promises as fsp, realpathSync } from 'node:fs';
import { spawn as nodeSpawn } from 'node:child_process';
/**
 * Phase 5 — production CommandIo factory.
 *
 * The CLI is invoked from a real terminal. Wrap node:fs + node:child_process
 * in the small adapter surface the commands consume; tests replace these with
 * deterministic in-memory implementations.
 */
export function createDefaultIo() {
    return {
        out: (line) => process.stdout.write(`${line}\n`),
        err: (line) => process.stderr.write(`${line}\n`),
        cwd: process.cwd(),
        env: { ...process.env },
        spawn: defaultSpawn,
        fs: defaultFs,
    };
}
export const defaultFs = {
    async exists(path) {
        return existsSync(path);
    },
    async readFile(path) {
        return fsp.readFile(path, 'utf8');
    },
    async writeFile(path, contents) {
        await fsp.writeFile(path, contents, 'utf8');
    },
    async mkdir(path, recursive = true) {
        await fsp.mkdir(path, { recursive });
    },
    async realpath(path) {
        try {
            return realpathSync(path);
        }
        catch {
            return path;
        }
    },
};
export const defaultSpawn = (command, args, options) => spawnProcess(command, args, options);
function spawnProcess(command, args, options = {}) {
    return new Promise((resolveResult) => {
        const spawnOptions = {
            cwd: options.cwd,
            env: { ...process.env, ...(options.env ?? {}) },
        };
        if (options.inheritStdio) {
            const child = nodeSpawn(command, args, { ...spawnOptions, stdio: 'inherit' });
            child.on('close', (code) => resolveResult({ exitCode: code ?? 0, stdout: '', stderr: '' }));
            child.on('error', () => resolveResult({ exitCode: 1, stdout: '', stderr: 'spawn error' }));
            return;
        }
        let stdout = '';
        let stderr = '';
        const child = nodeSpawn(command, args, spawnOptions);
        child.stdout?.on('data', (chunk) => { stdout += chunk.toString(); });
        child.stderr?.on('data', (chunk) => { stderr += chunk.toString(); });
        let timer;
        if (options.timeoutMs && Number.isFinite(options.timeoutMs)) {
            timer = setTimeout(() => {
                child.kill('SIGTERM');
            }, options.timeoutMs);
        }
        child.on('close', (code) => {
            if (timer)
                clearTimeout(timer);
            resolveResult({ exitCode: code ?? 0, stdout, stderr });
        });
        child.on('error', (error) => {
            if (timer)
                clearTimeout(timer);
            resolveResult({ exitCode: 1, stdout, stderr: `${stderr}${error.message}` });
        });
    });
}
