"use strict";
/**
 * Copilot Package Manager
 *
 * Self-managed npm package installation for copilot-api:
 * - Installs copilot-api locally to ~/.ccs/copilot/
 * - Auto-updates to latest version (like CLIProxy binary manager)
 * - Version caching to avoid hitting npm registry on every run
 * - Retry logic with exponential backoff
 *
 * Pattern: Mirrors CLIProxy BinaryManager but for npm packages
 */
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.getCopilotApiInfo = exports.uninstallCopilotApi = exports.installCopilotApiVersion = exports.ensureCopilotApi = exports.checkForUpdates = exports.clearPinnedVersion = exports.savePinnedVersion = exports.getPinnedVersion = exports.getInstalledVersion = exports.isCopilotApiInstalled = exports.getVersionPinPath = exports.getCopilotApiBinPath = exports.getCopilotDir = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const progress_indicator_1 = require("../utils/progress-indicator");
const ui_1 = require("../utils/ui");
const config_loader_facade_1 = require("../config/config-loader-facade");
/** Cache duration for version check (1 hour in milliseconds) */
const VERSION_CACHE_DURATION_MS = 60 * 60 * 1000;
/** Version pin file name */
const VERSION_PIN_FILE = '.version-pin';
/** Package name to install */
const COPILOT_API_PACKAGE = 'copilot-api';
/**
 * Get copilot base directory
 * All copilot-related files are stored under ~/.ccs/copilot/
 */
function getCopilotDir() {
    return path.join((0, config_loader_facade_1.getCcsDir)(), 'copilot');
}
exports.getCopilotDir = getCopilotDir;
/**
 * Get path to copilot-api binary
 */
function getCopilotApiBinPath() {
    const binName = process.platform === 'win32' ? 'copilot-api.cmd' : 'copilot-api';
    return path.join(getCopilotDir(), 'node_modules', '.bin', binName);
}
exports.getCopilotApiBinPath = getCopilotApiBinPath;
/**
 * Get path to package.json
 */
function getPackageJsonPath() {
    return path.join(getCopilotDir(), 'package.json');
}
/**
 * Get path to version file
 */
function getVersionFilePath() {
    return path.join(getCopilotDir(), '.version');
}
/**
 * Get path to version cache file
 */
function getVersionCachePath() {
    return path.join(getCopilotDir(), '.version-cache.json');
}
/**
 * Get path to version pin file
 */
function getVersionPinPath() {
    return path.join(getCopilotDir(), VERSION_PIN_FILE);
}
exports.getVersionPinPath = getVersionPinPath;
/**
 * Check if copilot-api is installed locally
 */
function isCopilotApiInstalled() {
    return fs.existsSync(getCopilotApiBinPath());
}
exports.isCopilotApiInstalled = isCopilotApiInstalled;
/**
 * Get installed version from .version file
 */
function getInstalledVersion() {
    const versionFile = getVersionFilePath();
    if (fs.existsSync(versionFile)) {
        try {
            return fs.readFileSync(versionFile, 'utf8').trim();
        }
        catch {
            return null;
        }
    }
    return null;
}
exports.getInstalledVersion = getInstalledVersion;
/**
 * Save installed version to file
 */
function saveInstalledVersion(version) {
    const versionFile = getVersionFilePath();
    try {
        fs.writeFileSync(versionFile, version, 'utf8');
    }
    catch {
        // Silent fail - not critical
    }
}
/**
 * Get cached latest version if still valid
 */
function getCachedLatestVersion() {
    const cachePath = getVersionCachePath();
    if (!fs.existsSync(cachePath)) {
        return null;
    }
    try {
        const content = fs.readFileSync(cachePath, 'utf8');
        const cache = JSON.parse(content);
        // Check if cache is still valid
        if (Date.now() - cache.checkedAt < VERSION_CACHE_DURATION_MS) {
            return cache.latestVersion;
        }
        // Cache expired
        return null;
    }
    catch {
        return null;
    }
}
/**
 * Cache latest version for future checks
 */
function cacheLatestVersion(version) {
    const cachePath = getVersionCachePath();
    const cache = {
        latestVersion: version,
        checkedAt: Date.now(),
    };
    try {
        fs.mkdirSync(path.dirname(cachePath), { recursive: true });
        fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf8');
    }
    catch {
        // Silent fail - caching is optional
    }
}
/**
 * Fetch latest version from npm registry
 */
async function fetchLatestVersion() {
    return new Promise((resolve, reject) => {
        const result = (0, child_process_1.spawnSync)('npm', ['view', COPILOT_API_PACKAGE, 'version'], {
            encoding: 'utf8',
            timeout: 15000,
            shell: process.platform === 'win32',
        });
        if (result.status === 0 && result.stdout) {
            resolve(result.stdout.trim());
        }
        else {
            reject(new Error(result.stderr || 'Failed to fetch version from npm'));
        }
    });
}
/**
 * Compare semver versions (true if latest > current)
 */
function isNewerVersion(latest, current) {
    const latestParts = latest.split('.').map((p) => parseInt(p, 10) || 0);
    const currentParts = current.split('.').map((p) => parseInt(p, 10) || 0);
    // Pad arrays to same length
    while (latestParts.length < 3)
        latestParts.push(0);
    while (currentParts.length < 3)
        currentParts.push(0);
    for (let i = 0; i < 3; i++) {
        if (latestParts[i] > currentParts[i])
            return true;
        if (latestParts[i] < currentParts[i])
            return false;
    }
    return false; // Equal versions
}
/**
 * Get pinned version if one exists
 */
function getPinnedVersion() {
    const pinPath = getVersionPinPath();
    if (!fs.existsSync(pinPath)) {
        return null;
    }
    try {
        return fs.readFileSync(pinPath, 'utf8').trim();
    }
    catch {
        return null;
    }
}
exports.getPinnedVersion = getPinnedVersion;
/**
 * Save pinned version
 */
function savePinnedVersion(version) {
    const pinPath = getVersionPinPath();
    try {
        fs.mkdirSync(path.dirname(pinPath), { recursive: true });
        fs.writeFileSync(pinPath, version, 'utf8');
    }
    catch {
        // Silent fail
    }
}
exports.savePinnedVersion = savePinnedVersion;
/**
 * Clear pinned version
 */
function clearPinnedVersion() {
    const pinPath = getVersionPinPath();
    if (fs.existsSync(pinPath)) {
        try {
            fs.unlinkSync(pinPath);
        }
        catch {
            // Silent fail
        }
    }
}
exports.clearPinnedVersion = clearPinnedVersion;
/**
 * Check for updates
 */
async function checkForUpdates() {
    const currentVersion = getInstalledVersion() || '0.0.0';
    // Try cache first
    const cachedVersion = getCachedLatestVersion();
    if (cachedVersion) {
        return {
            hasUpdate: isNewerVersion(cachedVersion, currentVersion),
            currentVersion,
            latestVersion: cachedVersion,
            fromCache: true,
        };
    }
    // Fetch from npm registry
    try {
        const latestVersion = await fetchLatestVersion();
        cacheLatestVersion(latestVersion);
        return {
            hasUpdate: isNewerVersion(latestVersion, currentVersion),
            currentVersion,
            latestVersion,
            fromCache: false,
        };
    }
    catch {
        // Return no update if fetch fails
        return {
            hasUpdate: false,
            currentVersion,
            latestVersion: currentVersion,
            fromCache: false,
        };
    }
}
exports.checkForUpdates = checkForUpdates;
/**
 * Run npm install in copilot directory
 */
async function runNpmInstall(version) {
    const copilotDir = getCopilotDir();
    // Ensure directory exists
    fs.mkdirSync(copilotDir, { recursive: true });
    // Create package.json with versioned or latest dependency
    const packageJson = {
        name: 'ccs-copilot-local',
        private: true,
        dependencies: {
            [COPILOT_API_PACKAGE]: version || 'latest',
        },
    };
    fs.writeFileSync(getPackageJsonPath(), JSON.stringify(packageJson, null, 2), 'utf8');
    return new Promise((resolve) => {
        const proc = (0, child_process_1.spawn)('npm', ['install', '--prefer-offline', '--no-audit', '--no-fund'], {
            cwd: copilotDir,
            stdio: ['ignore', 'pipe', 'pipe'],
            shell: process.platform === 'win32',
        });
        let stderr = '';
        proc.stderr?.on('data', (data) => {
            stderr += data.toString();
        });
        proc.on('close', (code) => {
            if (code === 0) {
                // Get installed version from package-lock.json or package.json
                try {
                    const lockPath = path.join(copilotDir, 'package-lock.json');
                    if (fs.existsSync(lockPath)) {
                        const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
                        const installedVersion = lock.packages?.['node_modules/copilot-api']?.version ||
                            lock.dependencies?.[COPILOT_API_PACKAGE]?.version;
                        if (installedVersion) {
                            saveInstalledVersion(installedVersion);
                            resolve({ success: true, version: installedVersion });
                            return;
                        }
                    }
                }
                catch {
                    // Fallback
                }
                resolve({ success: true, version: version || 'latest' });
            }
            else {
                resolve({ success: false, error: stderr || `npm install failed with code ${code}` });
            }
        });
        proc.on('error', (err) => {
            resolve({ success: false, error: err.message });
        });
    });
}
/**
 * Ensure copilot-api is available (install if missing, update if outdated)
 * @returns Path to copilot-api binary
 */
async function ensureCopilotApi(verbose = false) {
    const binPath = getCopilotApiBinPath();
    const pinnedVersion = getPinnedVersion();
    // Check if already installed
    if (isCopilotApiInstalled()) {
        if (verbose) {
            console.error(`[copilot] Binary exists: ${binPath}`);
        }
        // Skip auto-update if version is pinned
        if (pinnedVersion) {
            if (verbose) {
                console.error(`[copilot] Pinned version mode: skipping auto-update`);
            }
            return binPath;
        }
        // Check for updates (non-blocking for UX)
        try {
            const updateResult = await checkForUpdates();
            if (updateResult.hasUpdate) {
                console.log((0, ui_1.info)(`copilot-api update available: v${updateResult.currentVersion} -> v${updateResult.latestVersion}`));
                console.log((0, ui_1.info)('Updating copilot-api...'));
                const spinner = new progress_indicator_1.ProgressIndicator('Updating copilot-api');
                spinner.start();
                const result = await runNpmInstall(updateResult.latestVersion);
                if (result.success) {
                    spinner.succeed('copilot-api updated');
                    console.log((0, ui_1.ok)(`copilot-api v${result.version} installed successfully`));
                }
                else {
                    spinner.fail('Update failed (non-blocking)');
                    if (verbose) {
                        console.error(`[copilot] Update failed: ${result.error}`);
                    }
                }
            }
        }
        catch (error) {
            // Silent fail - don't block startup if update check fails
            if (verbose) {
                console.error(`[copilot] Update check failed: ${error.message}`);
            }
        }
        return binPath;
    }
    // Not installed - install now
    console.log((0, ui_1.info)('copilot-api not found, installing...'));
    const spinner = new progress_indicator_1.ProgressIndicator('Installing copilot-api');
    spinner.start();
    try {
        // Get target version
        let targetVersion;
        if (pinnedVersion) {
            targetVersion = pinnedVersion;
            if (verbose) {
                console.error(`[copilot] Using pinned version: ${pinnedVersion}`);
            }
        }
        else {
            // Fetch latest version
            try {
                targetVersion = await fetchLatestVersion();
                cacheLatestVersion(targetVersion);
            }
            catch {
                // Use 'latest' if fetch fails
                targetVersion = undefined;
            }
        }
        const result = await runNpmInstall(targetVersion);
        if (result.success) {
            spinner.succeed('copilot-api installed');
            console.log((0, ui_1.ok)(`copilot-api v${result.version} installed successfully`));
            return binPath;
        }
        else {
            spinner.fail('Installation failed');
            throw new Error(result.error || 'npm install failed');
        }
    }
    catch (error) {
        spinner.fail('Installation failed');
        throw error;
    }
}
exports.ensureCopilotApi = ensureCopilotApi;
/**
 * Install a specific version of copilot-api
 */
async function installCopilotApiVersion(version, _verbose = false) {
    const spinner = new progress_indicator_1.ProgressIndicator(`Installing copilot-api v${version}`);
    spinner.start();
    try {
        const result = await runNpmInstall(version);
        if (result.success) {
            spinner.succeed('copilot-api installed');
            console.log((0, ui_1.ok)(`copilot-api v${result.version} installed successfully`));
        }
        else {
            spinner.fail('Installation failed');
            throw new Error(result.error || 'npm install failed');
        }
    }
    catch (error) {
        spinner.fail('Installation failed');
        throw error;
    }
}
exports.installCopilotApiVersion = installCopilotApiVersion;
/**
 * Uninstall copilot-api (remove ~/.ccs/copilot directory)
 */
function uninstallCopilotApi() {
    const copilotDir = getCopilotDir();
    if (fs.existsSync(copilotDir)) {
        fs.rmSync(copilotDir, { recursive: true, force: true });
    }
}
exports.uninstallCopilotApi = uninstallCopilotApi;
/**
 * Get copilot-api info
 */
function getCopilotApiInfo() {
    return {
        installed: isCopilotApiInstalled(),
        version: getInstalledVersion(),
        path: getCopilotApiBinPath(),
        pinnedVersion: getPinnedVersion(),
    };
}
exports.getCopilotApiInfo = getCopilotApiInfo;
exports.default = {
    getCopilotDir,
    getCopilotApiBinPath,
    isCopilotApiInstalled,
    getInstalledVersion,
    getPinnedVersion,
    savePinnedVersion,
    clearPinnedVersion,
    checkForUpdates,
    ensureCopilotApi,
    installCopilotApiVersion,
    uninstallCopilotApi,
    getCopilotApiInfo,
};
//# sourceMappingURL=copilot-package-manager.js.map