"use strict";
/**
 * Update Command Handler
 *
 * Handles `ccs update` command - checks for updates and installs latest version.
 * Uses npm/yarn/pnpm/bun package managers exclusively.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleUpdateCommand = void 0;
const child_process_1 = require("child_process");
const ui_1 = require("../utils/ui");
const package_manager_detector_1 = require("../utils/package-manager-detector");
const update_checker_1 = require("../utils/update-checker");
const version_1 = require("../utils/version");
async function loadCheckForUpdates(currentVersion, interactive, channel, targetTag) {
    const { checkForUpdates } = await Promise.resolve().then(() => __importStar(require('../utils/update-checker')));
    return checkForUpdates(currentVersion, interactive, channel, targetTag);
}
const defaultDeps = {
    initUI: ui_1.initUI,
    getVersion: version_1.getVersion,
    log: console.log,
    exit: process.exit.bind(process),
    detectCurrentInstall: package_manager_detector_1.detectCurrentInstall,
    buildPackageManagerEnv: package_manager_detector_1.buildPackageManagerEnv,
    formatManualUpdateCommand: package_manager_detector_1.formatManualUpdateCommand,
    readInstalledPackageState: package_manager_detector_1.readInstalledPackageState,
    compareVersionsWithPrerelease: update_checker_1.compareVersionsWithPrerelease,
    checkForUpdates: loadCheckForUpdates,
    spawn: child_process_1.spawn,
};
async function resolveTargetVersion(currentVersion, targetTag, deps) {
    const result = await deps.checkForUpdates(currentVersion, true, 'npm', targetTag);
    if (result.status === 'update_available' && result.latest) {
        return result.latest;
    }
    if (result.status === 'no_update') {
        return currentVersion;
    }
    return undefined;
}
async function handleUpdateCommand(options = {}, injectedDeps = {}) {
    const deps = { ...defaultDeps, ...injectedDeps };
    await deps.initUI();
    const { force = false, beta = false } = options;
    const targetTag = beta ? 'dev' : 'latest';
    const currentInstall = deps.detectCurrentInstall();
    const currentVersion = deps.getVersion();
    deps.log('');
    deps.log((0, ui_1.header)('Checking for updates...'));
    deps.log('');
    // Force reinstall - skip update check
    if (force) {
        deps.log((0, ui_1.info)(`Force reinstall from @${targetTag} channel...`));
        deps.log('');
        const expectedVersion = await resolveTargetVersion(currentVersion, targetTag, deps);
        await performNpmUpdate(currentInstall, targetTag, true, expectedVersion, deps);
        return;
    }
    const updateResult = await deps.checkForUpdates(currentVersion, true, 'npm', targetTag);
    if (updateResult.status === 'check_failed') {
        handleCheckFailed(updateResult.message ?? 'Update check failed', targetTag, currentInstall, deps);
        return;
    }
    if (updateResult.status === 'no_update') {
        handleNoUpdate(updateResult.reason, currentVersion, deps);
        return;
    }
    // Update available
    deps.log((0, ui_1.warn)(`Update available: ${updateResult.current} -> ${updateResult.latest}`));
    deps.log('');
    // Check if this is a downgrade (e.g., stable to older dev)
    const isDowngrade = updateResult.latest &&
        updateResult.current &&
        deps.compareVersionsWithPrerelease(updateResult.latest, updateResult.current) < 0;
    // This happens when stable user requests @dev but @dev base is older
    if (isDowngrade && beta) {
        deps.log((0, ui_1.warn)('WARNING: Downgrading from ' +
            (updateResult.current || 'unknown') +
            ' to ' +
            (updateResult.latest || 'unknown')));
        deps.log((0, ui_1.warn)('Dev channel may be behind stable.'));
        deps.log('');
    }
    // Show beta warning
    if (beta) {
        deps.log((0, ui_1.warn)('Installing from @dev channel (unstable)'));
        deps.log((0, ui_1.warn)('Not recommended for production use'));
        deps.log((0, ui_1.info)('Use `ccs update` (without --beta) to return to stable'));
        deps.log('');
    }
    await performNpmUpdate(currentInstall, targetTag, false, updateResult.latest, deps);
}
exports.handleUpdateCommand = handleUpdateCommand;
/**
 * Handle failed update check
 */
function handleCheckFailed(message, targetTag = 'latest', currentInstall = defaultDeps.detectCurrentInstall(), deps = defaultDeps) {
    deps.log((0, ui_1.fail)(message));
    deps.log('');
    deps.log((0, ui_1.warn)('Possible causes:'));
    deps.log('  - Network connection issues');
    deps.log('  - Firewall blocking requests');
    deps.log('  - GitHub/npm API temporarily unavailable');
    deps.log('');
    deps.log('Try again later or update manually:');
    deps.log((0, ui_1.color)(`  ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
    deps.log('');
    deps.exit(1);
}
/**
 * Handle no update available
 */
function handleNoUpdate(reason, version, deps) {
    let message = `You are already on the latest version (${version})`;
    switch (reason) {
        case 'dismissed':
            message = `Update dismissed. You are on version ${version}`;
            deps.log((0, ui_1.warn)(message));
            break;
        case 'cached':
            message = `No updates available (cached result). You are on version ${version}`;
            deps.log((0, ui_1.info)(message));
            break;
        default:
            deps.log((0, ui_1.ok)(message));
    }
    deps.log('');
    deps.exit(0);
}
/**
 * Perform update verification against the current install.
 */
async function verifyCurrentInstallVersion(currentInstall, targetTag, expectedVersion, previousState, isReinstall = false, deps = defaultDeps) {
    const nextState = deps.readInstalledPackageState(currentInstall);
    const installedVersion = nextState.version;
    if (!installedVersion) {
        deps.log('');
        deps.log((0, ui_1.fail)('Update finished, but CCS could not verify the current installation version.'));
        deps.log('');
        deps.log('Current install remains ambiguous. Re-run manually:');
        deps.log((0, ui_1.color)(`  ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
        deps.log('');
        deps.exit(1);
        return;
    }
    const installChanged = previousState !== undefined &&
        (previousState.version !== nextState.version ||
            previousState.packageJsonMtimeMs !== nextState.packageJsonMtimeMs ||
            previousState.scriptMtimeMs !== nextState.scriptMtimeMs);
    if (expectedVersion && installedVersion !== expectedVersion) {
        const postUpdateResult = await deps.checkForUpdates(installedVersion, true, 'npm', targetTag);
        if (postUpdateResult.status === 'no_update') {
            return;
        }
        if (postUpdateResult.status === 'update_available' &&
            postUpdateResult.latest === installedVersion) {
            return;
        }
        const comparison = deps.compareVersionsWithPrerelease(installedVersion, expectedVersion);
        if (comparison < 0 || installedVersion === previousState?.version) {
            deps.log('');
            deps.log((0, ui_1.fail)(`Update completed outside the current installation. Current binary still reports ${installedVersion}; expected ${expectedVersion}.`));
            if (previousState?.version && previousState.version === installedVersion) {
                deps.log((0, ui_1.warn)(`The current install path did not change from ${previousState.version}; another package manager likely updated a different copy of CCS.`));
            }
            deps.log('');
            deps.log('Re-run manually against the current install:');
            deps.log((0, ui_1.color)(`  ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
            deps.log('');
            deps.exit(1);
            return;
        }
    }
    if (isReinstall &&
        previousState?.version &&
        installedVersion === previousState.version &&
        !installChanged) {
        deps.log('');
        deps.log((0, ui_1.warn)(`Reinstall completed, but CCS could not prove that the current installation changed from ${previousState.version}. Verify the current binary manually if this reinstall was meant to repair a same-version install.`));
    }
}
function runChildProcess(deps, command, args, options) {
    return new Promise((resolve, reject) => {
        const { isWindows, env, filterCleanupWarnings = false } = options;
        const child = isWindows
            ? deps.spawn(`${command} ${args.join(' ')}`, [], {
                stdio: ['inherit', 'inherit', 'pipe'],
                shell: true,
                env: { ...env, NODE_NO_WARNINGS: '1' },
            })
            : deps.spawn(command, args, { stdio: 'inherit', env });
        if (isWindows && filterCleanupWarnings && child.stderr) {
            let stderrBuffer = '';
            child.stderr.on('data', (data) => {
                stderrBuffer += data.toString();
                const lines = stderrBuffer.split('\n');
                stderrBuffer = lines.pop() || '';
                for (const line of lines) {
                    if (!/npm warn cleanup/i.test(line)) {
                        process.stderr.write(line + '\n');
                    }
                }
            });
            child.stderr.on('close', () => {
                if (stderrBuffer && !/npm warn cleanup/i.test(stderrBuffer)) {
                    process.stderr.write(stderrBuffer);
                }
            });
        }
        child.on('error', reject);
        child.on('exit', (code) => resolve(code ?? 0));
    });
}
async function performNpmUpdate(currentInstall, targetTag = 'latest', isReinstall = false, expectedVersion, deps = defaultDeps) {
    const packageManager = currentInstall.manager;
    let updateCommand;
    let updateArgs;
    let cacheCommand;
    let cacheArgs;
    const childEnv = deps.buildPackageManagerEnv(currentInstall);
    const previousState = deps.readInstalledPackageState(currentInstall);
    switch (packageManager) {
        case 'npm':
            updateCommand = 'npm';
            updateArgs = ['install', '-g', `@kaitranntt/ccs@${targetTag}`];
            cacheCommand = 'npm';
            cacheArgs = ['cache', 'clean', '--force'];
            break;
        case 'yarn':
            updateCommand = 'yarn';
            updateArgs = ['global', 'add', `@kaitranntt/ccs@${targetTag}`];
            cacheCommand = 'yarn';
            cacheArgs = ['cache', 'clean'];
            break;
        case 'pnpm':
            updateCommand = 'pnpm';
            updateArgs = ['add', '-g', `@kaitranntt/ccs@${targetTag}`];
            cacheCommand = 'pnpm';
            cacheArgs = ['store', 'prune'];
            break;
        case 'bun':
            updateCommand = 'bun';
            updateArgs = ['add', '-g', `@kaitranntt/ccs@${targetTag}`];
            // On Windows, bun's global bin symlink may not update properly without removal first
            // Pre-remove to ensure clean reinstall (mirrors dev-install.sh behavior)
            cacheCommand = process.platform === 'win32' ? 'bun' : null;
            cacheArgs = process.platform === 'win32' ? ['remove', '-g', '@kaitranntt/ccs'] : null;
            break;
        default:
            updateCommand = 'npm';
            updateArgs = ['install', '-g', `@kaitranntt/ccs@${targetTag}`];
            cacheCommand = 'npm';
            cacheArgs = ['cache', 'clean', '--force'];
    }
    deps.log((0, ui_1.info)(`${isReinstall ? 'Reinstalling' : 'Updating'} via ${packageManager}...`));
    deps.log('');
    const isWindows = process.platform === 'win32';
    if (cacheCommand && cacheArgs) {
        // For bun on Windows, we pre-remove instead of cache clear
        const isBunPreRemove = packageManager === 'bun' && cacheArgs.includes('remove');
        const stepMessage = isBunPreRemove
            ? 'Removing existing installation...'
            : 'Clearing package cache...';
        const failMessage = isBunPreRemove
            ? 'Pre-removal failed, proceeding anyway...'
            : 'Cache clearing failed, proceeding anyway...';
        deps.log((0, ui_1.info)(stepMessage));
        try {
            const cacheCode = await runChildProcess(deps, cacheCommand, cacheArgs, {
                isWindows,
                env: childEnv,
            });
            if (cacheCode !== 0) {
                deps.log((0, ui_1.warn)(failMessage));
            }
        }
        catch {
            deps.log((0, ui_1.warn)(failMessage));
        }
    }
    try {
        const exitCode = await runChildProcess(deps, updateCommand, updateArgs, {
            isWindows,
            env: childEnv,
            filterCleanupWarnings: true,
        });
        if (exitCode === 0) {
            if (expectedVersion || previousState?.version) {
                await verifyCurrentInstallVersion(currentInstall, targetTag, expectedVersion, previousState, isReinstall, deps);
            }
            deps.log('');
            deps.log((0, ui_1.ok)(`${isReinstall ? 'Reinstall' : 'Update'} successful!`));
            deps.log('');
            deps.log(`Run ${(0, ui_1.color)('ccs --version', 'command')} to verify`);
            deps.log((0, ui_1.info)(`Tip: Use ${(0, ui_1.color)('ccs config', 'command')} for web-based configuration`));
            deps.log('');
        }
        else {
            deps.log('');
            deps.log((0, ui_1.fail)(`${isReinstall ? 'Reinstall' : 'Update'} failed`));
            deps.log('');
            deps.log('Try manually:');
            deps.log((0, ui_1.color)(`  ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
            deps.log('');
        }
        deps.exit(exitCode || 0);
    }
    catch {
        deps.log('');
        deps.log((0, ui_1.fail)(`Failed to run ${packageManager} ${isReinstall ? 'reinstall' : 'update'}`));
        deps.log('');
        deps.log('Try manually:');
        deps.log((0, ui_1.color)(`  ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
        deps.log('');
        deps.exit(1);
    }
}
//# sourceMappingURL=update-command.js.map