/**
 * Foreground update execution path.
 *
 * Runs the updater command in the startup flow. Updaters such as npm can emit
 * huge cleanup dumps on Windows when a global install tree is locked or
 * half-deleted, so stdout/stderr are captured and summarized instead of being
 * streamed directly into the user's terminal.
 */
import { spawn } from 'child_process';
import { appendFileSync, mkdirSync } from 'fs';
import { dirname } from 'path';
import { getShellConfig, getShellEnv } from '../app/tools/bash-runtime.js';
import { getDebugLogPath } from '../config.js';
const MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
export async function runForegroundUpdate(request, deps = {}) {
    const resolveShell = deps.resolveShell ?? getShellConfig;
    const resolveEnv = deps.resolveEnv ?? getShellEnv;
    const spawnProcess = deps.spawnProcess ?? spawn;
    const resolveLogPath = deps.resolveLogPath ?? getDebugLogPath;
    const shellConfig = resolveShell();
    return await new Promise((resolve, reject) => {
        const output = [];
        let outputBytes = 0;
        let outputTruncated = false;
        const child = spawnProcess(shellConfig.shell, [...shellConfig.args, request.command], {
            cwd: request.cwd ?? process.cwd(),
            env: resolveEnv(),
            stdio: ['ignore', 'pipe', 'pipe']
        });
        const capture = (chunk) => {
            const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
            const remaining = MAX_CAPTURED_OUTPUT_BYTES - outputBytes;
            if (remaining > 0) {
                output.push(buffer.subarray(0, remaining));
                outputBytes += Math.min(buffer.length, remaining);
            }
            if (buffer.length > remaining) {
                outputTruncated = true;
            }
        };
        child.stdout?.on('data', capture);
        child.stderr?.on('data', capture);
        child.once('error', reject);
        child.once('close', (exitCode, signal) => {
            if (exitCode === 0) {
                resolve({
                    kind: 'success',
                    exitCode,
                    signal
                });
                return;
            }
            const capturedOutput = Buffer.concat(output).toString('utf-8');
            const logPath = writeUpdateFailureLog({
                command: request.command,
                exitCode,
                signal,
                output: capturedOutput,
                truncated: outputTruncated,
                resolveLogPath
            });
            resolve({
                kind: 'failed',
                exitCode,
                signal,
                message: summarizeUpdateFailure(capturedOutput, request.command),
                logPath
            });
        });
    });
}
function summarizeUpdateFailure(output, updateCommand) {
    if (/EEXIST[\s\S]*shortcut\.cmd/i.test(output) ||
        /File exists:[\s\S]*shortcut\.cmd/i.test(output)) {
        return [
            'ShortcutXL could not update because an existing global `shortcut` command blocked npm from replacing it.',
            `Close any running ShortcutXL terminals, remove the existing global ShortcutXL package, then retry \`${updateCommand}\`.`
        ].join(' ');
    }
    if (/EPERM|ENOTEMPTY|ENOENT/i.test(output) &&
        /AppData[\\/]+Roaming[\\/]+npm[\\/]+node_modules[\\/]+shortcutxl/i.test(output)) {
        return [
            'ShortcutXL could not update because npm could not clean up the previous global install.',
            `Close ShortcutXL terminals and retry \`${updateCommand}\`.`
        ].join(' ');
    }
    return `ShortcutXL could not update automatically. Retry with \`${updateCommand}\`.`;
}
function writeUpdateFailureLog({ command, exitCode, signal, output, truncated, resolveLogPath }) {
    try {
        const logPath = resolveLogPath();
        mkdirSync(dirname(logPath), { recursive: true });
        appendFileSync(logPath, [
            '',
            '--- ShortcutXL update failure ---',
            `time=${new Date().toISOString()}`,
            `command=${command}`,
            `exitCode=${String(exitCode)}`,
            `signal=${String(signal)}`,
            truncated ? `outputTruncatedAfterBytes=${MAX_CAPTURED_OUTPUT_BYTES}` : undefined,
            output
        ]
            .filter((line) => line !== undefined)
            .join('\n'), 'utf-8');
        return logPath;
    }
    catch {
        return undefined;
    }
}
//# sourceMappingURL=update-action.js.map