"use strict";
/**
 * Binary Manager for CLIProxyAPI
 *
 * Facade pattern wrapper for modular binary management.
 * Pattern: Mirrors npm install behavior (fast check, download only when needed)
 */
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.migrateVersionPin = exports.isVersionPinned = exports.clearPinnedVersion = exports.savePinnedVersion = exports.getPinnedVersion = exports.getVersionPinPath = exports.checkCliproxyUpdate = exports.fetchLatestCliproxyVersion = exports.installCliproxyVersion = exports.getInstalledCliproxyVersion = exports.getCLIProxyPath = exports.isCLIProxyInstalled = exports.ensureCLIProxyBinary = exports.BinaryManager = exports.getConfiguredBackend = exports.getStoredConfiguredBackend = exports.resolveLocalBackend = exports.syncPlusFallbackStateIfNeeded = exports.getPlusBackendUnavailableMessage = exports.CLIPROXY_PLUS_TRACKING_URL = exports.CLIPROXY_PLUS_FORK_URL = exports.CLIPROXY_PLUS_FALLBACK_TRACKING_URL = exports.CLIPROXY_DELETED_PLUS_REPO = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ui_1 = require("../utils/ui");
const config_generator_1 = require("./config/config-generator");
const port_manager_1 = require("./config/port-manager");
const platform_detector_1 = require("./binary/platform-detector");
const proxy_lifecycle_service_1 = require("./services/proxy-lifecycle-service");
const port_utils_1 = require("../utils/port-utils");
const binary_1 = require("./binary");
Object.defineProperty(exports, "getPinnedVersion", { enumerable: true, get: function () { return binary_1.getPinnedVersion; } });
Object.defineProperty(exports, "savePinnedVersion", { enumerable: true, get: function () { return binary_1.savePinnedVersion; } });
Object.defineProperty(exports, "clearPinnedVersion", { enumerable: true, get: function () { return binary_1.clearPinnedVersion; } });
Object.defineProperty(exports, "isVersionPinned", { enumerable: true, get: function () { return binary_1.isVersionPinned; } });
Object.defineProperty(exports, "getVersionPinPath", { enumerable: true, get: function () { return binary_1.getVersionPinPath; } });
Object.defineProperty(exports, "migrateVersionPin", { enumerable: true, get: function () { return binary_1.migrateVersionPin; } });
const version_cache_1 = require("./binary/version-cache");
const config_loader_facade_1 = require("../config/config-loader-facade");
exports.CLIPROXY_DELETED_PLUS_REPO = 'router-for-me/CLIProxyAPIPlus';
exports.CLIPROXY_PLUS_FALLBACK_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1062';
exports.CLIPROXY_PLUS_FORK_URL = 'https://github.com/kaitranntt/CLIProxyAPIPlus';
exports.CLIPROXY_PLUS_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1065';
/**
 * Track whether we've already warned the user about the legacy Plus fallback
 * this process lifetime. Prevents spamming the warning on every command.
 */
let plusFallbackWarned = false;
function emitPlusFallbackWarning() {
    if (plusFallbackWarned)
        return;
    plusFallbackWarned = true;
    process.stderr.write(`${(0, ui_1.warn)('Legacy CLIProxyAPIPlus upstream repo is unavailable; local CLIProxy is falling back to ' +
        '`backend: original`. Run `ccs config` to use the maintained Plus fork. ' +
        `Tracking: ${exports.CLIPROXY_PLUS_FALLBACK_TRACKING_URL}`)}\n`);
}
/**
 * Track whether we've already shown the optional Plus backend notice this
 * process lifetime. Prevents spamming the message on every command.
 */
let plusBackendInfoShown = false;
function emitPlusBackendInfo() {
    if (plusBackendInfoShown)
        return;
    plusBackendInfoShown = true;
    process.stderr.write(`${(0, ui_1.info)('Using optional community-maintained CLIProxyAPIPlus backend. Stay on ' +
        '`backend: original` unless you need plus-only providers. ' +
        `Fork: ${exports.CLIPROXY_PLUS_FORK_URL} ` +
        `Tracking: ${exports.CLIPROXY_PLUS_TRACKING_URL}`)}\n`);
}
function getPlusBackendUnavailableMessage(provider) {
    const prefix = provider
        ? `${provider} requires CLIProxyAPIPlus,`
        : 'This provider requires CLIProxyAPIPlus,';
    return (`${prefix} which is available through the optional community-maintained ` +
        `\`backend: plus\` fork. Set \`cliproxy.backend: plus\` in config.yaml ` +
        `only if you need plus-only providers. Fork: ${exports.CLIPROXY_PLUS_FORK_URL}`);
}
exports.getPlusBackendUnavailableMessage = getPlusBackendUnavailableMessage;
function isPlusBackendFallbackActive() {
    return platform_detector_1.BACKEND_CONFIG.plus.repo === exports.CLIPROXY_DELETED_PLUS_REPO;
}
function copyFallbackStateIfMissing(sourcePath, targetPath) {
    if (!fs.existsSync(sourcePath) || fs.existsSync(targetPath))
        return;
    fs.mkdirSync(path.dirname(targetPath), { recursive: true });
    fs.copyFileSync(sourcePath, targetPath);
}
function syncPlusFallbackStateIfNeeded(configuredBackend) {
    if (configuredBackend !== 'plus' || !isPlusBackendFallbackActive())
        return;
    const plusDir = getBackendBinDir('plus');
    const originalDir = getBackendBinDir('original');
    copyFallbackStateIfMissing(path.join(plusDir, (0, platform_detector_1.getExecutableName)('plus')), path.join(originalDir, (0, platform_detector_1.getExecutableName)('original')));
    copyFallbackStateIfMissing(path.join(plusDir, '.version'), path.join(originalDir, '.version'));
    copyFallbackStateIfMissing((0, binary_1.getVersionPinPath)('plus'), (0, binary_1.getVersionPinPath)('original'));
    copyFallbackStateIfMissing((0, binary_1.getVersionCachePath)('plus'), (0, binary_1.getVersionCachePath)('original'));
    copyFallbackStateIfMissing((0, version_cache_1.getVersionListCachePath)('plus'), (0, version_cache_1.getVersionListCachePath)('original'));
}
exports.syncPlusFallbackStateIfNeeded = syncPlusFallbackStateIfNeeded;
function resolveLocalBackend(backend = platform_detector_1.DEFAULT_BACKEND, options = {}) {
    if (backend !== 'plus')
        return backend;
    if (isPlusBackendFallbackActive()) {
        syncPlusFallbackStateIfNeeded(backend);
        if (options.warnOnFallback) {
            emitPlusFallbackWarning();
        }
        return 'original';
    }
    if (options.notifyOnPlus) {
        emitPlusBackendInfo();
    }
    return backend;
}
exports.resolveLocalBackend = resolveLocalBackend;
function getConfiguredOrDefaultBackend() {
    try {
        const config = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)();
        return config.cliproxy?.backend || platform_detector_1.DEFAULT_BACKEND;
    }
    catch {
        return platform_detector_1.DEFAULT_BACKEND;
    }
}
function getStoredConfiguredBackend() {
    return getConfiguredOrDefaultBackend();
}
exports.getStoredConfiguredBackend = getStoredConfiguredBackend;
/**
 * Get backend from config. Plus remains opt-in and resolves to the CCS-maintained
 * community fork instead of degrading to original.
 */
function getConfiguredBackend(options = {}) {
    return resolveLocalBackend(getConfiguredOrDefaultBackend(), options);
}
exports.getConfiguredBackend = getConfiguredBackend;
/**
 * Get backend-specific binary directory.
 * Stores binaries in separate dirs: bin/original/ and bin/plus/
 */
function getBackendBinDir(backend = platform_detector_1.DEFAULT_BACKEND) {
    const baseDir = (0, config_generator_1.getBinDir)();
    return `${baseDir}/${backend}`;
}
/** Default configuration (uses backend from config.yaml or defaults to `DEFAULT_BACKEND`) */
function createDefaultConfig(backend = platform_detector_1.DEFAULT_BACKEND) {
    const backendConfig = platform_detector_1.BACKEND_CONFIG[backend];
    return {
        version: backendConfig.fallbackVersion,
        releaseUrl: `https://github.com/${backendConfig.repo}/releases/download`,
        binPath: getBackendBinDir(backend),
        maxRetries: 3,
        verbose: false,
        forceVersion: false,
        skipAutoUpdate: false,
        allowInstall: true,
        backend, // Pass backend for installer to use correct download URL
    };
}
/**
 * Binary Manager class for CLIProxyAPI binary lifecycle
 */
class BinaryManager {
    constructor(config = {}, backend) {
        const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
        this.backend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
        const defaultConfig = createDefaultConfig(this.backend);
        this.config = { ...defaultConfig, ...config };
    }
    /** Ensure binary is available (download if missing, update if outdated) */
    async ensureBinary() {
        return (0, binary_1.ensureBinary)(this.config);
    }
    /** Check for updates by comparing installed version with latest release */
    async checkForUpdates() {
        return (0, binary_1.checkForUpdates)(this.config.binPath, this.config.version, this.config.verbose, this.backend);
    }
    /** Get full path to binary executable */
    getBinaryPath() {
        return (0, binary_1.getBinaryPath)(this.config.binPath, this.backend);
    }
    /** Check if binary exists */
    isBinaryInstalled() {
        return (0, binary_1.isBinaryInstalled)(this.config.binPath, this.backend);
    }
    /** Get binary info if installed */
    async getBinaryInfo() {
        return (0, binary_1.getBinaryInfo)(this.config.binPath, this.config.version, this.backend);
    }
    /** Delete binary (for cleanup or reinstall) */
    deleteBinary() {
        (0, binary_1.deleteBinary)(this.config.binPath, this.config.verbose, this.backend);
    }
}
exports.BinaryManager = BinaryManager;
/** Convenience function respecting version pin */
async function ensureCLIProxyBinary(verbose = false, options = {}) {
    const configuredBackend = getConfiguredOrDefaultBackend();
    const backend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    // Migrate old shared pin to backend-specific location (one-time migration)
    (0, binary_1.migrateVersionPin)(backend);
    const pinnedVersion = (0, binary_1.getPinnedVersion)(backend);
    if (pinnedVersion) {
        if (verbose)
            console.error(`[cliproxy] Using pinned version: ${pinnedVersion}`);
        return new BinaryManager({
            version: pinnedVersion,
            verbose,
            forceVersion: true,
            skipAutoUpdate: options.skipAutoUpdate ?? false,
            allowInstall: options.allowInstall ?? true,
        }, backend).ensureBinary();
    }
    return new BinaryManager({
        verbose,
        skipAutoUpdate: options.skipAutoUpdate ?? false,
        allowInstall: options.allowInstall ?? true,
    }, backend).ensureBinary();
}
exports.ensureCLIProxyBinary = ensureCLIProxyBinary;
/** Check if CLIProxyAPI binary is installed */
function isCLIProxyInstalled(backend) {
    const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
    const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    return new BinaryManager({}, effectiveBackend).isBinaryInstalled();
}
exports.isCLIProxyInstalled = isCLIProxyInstalled;
/** Get CLIProxyAPI binary path (may not exist) */
function getCLIProxyPath(backend) {
    const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
    const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    return new BinaryManager({}, effectiveBackend).getBinaryPath();
}
exports.getCLIProxyPath = getCLIProxyPath;
/** Get installed CLIProxyAPI version from .version file */
function getInstalledCliproxyVersion(backend) {
    const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
    const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    return (0, binary_1.readInstalledVersion)(getBackendBinDir(effectiveBackend), platform_detector_1.BACKEND_CONFIG[effectiveBackend].fallbackVersion);
}
exports.getInstalledCliproxyVersion = getInstalledCliproxyVersion;
/** Install a specific version of CLIProxyAPI */
async function installCliproxyVersion(version, verbose = false, backend, deps = {}) {
    const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
    const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    const manager = deps.createManager?.({ version, verbose, forceVersion: true }, effectiveBackend) ??
        new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
    const stopProxyFn = deps.stopProxyFn ?? proxy_lifecycle_service_1.stopProxy;
    const waitForPortFreeFn = deps.waitForPortFreeFn ?? port_utils_1.waitForPortFree;
    const formatInfo = deps.formatInfo ?? ui_1.info;
    const formatWarn = deps.formatWarn ?? ui_1.warn;
    const getInstalledVersion = deps.getInstalledVersion ?? getInstalledCliproxyVersion;
    // Always attempt a best-effort stop first so we also catch untracked proxies
    // that are running without a session lock.
    if (verbose)
        console.log(formatInfo('Stopping running CLIProxy before update...'));
    const result = await stopProxyFn();
    if (result.stopped) {
        const stoppedPort = result.port ?? (0, port_manager_1.resolveLifecyclePort)();
        // Wait for port to be fully released
        const portFree = await waitForPortFreeFn(stoppedPort, 5000);
        if (!portFree && verbose) {
            console.log(formatWarn('Port did not free up in time, proceeding anyway...'));
        }
    }
    else if (verbose && result.error && result.error !== 'No active CLIProxy session found') {
        console.log(formatWarn(`Could not stop proxy: ${result.error}`));
    }
    if (manager.isBinaryInstalled()) {
        const label = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
        if (verbose)
            console.log(formatInfo(`Removing existing ${label} v${getInstalledVersion(effectiveBackend)}`));
        manager.deleteBinary();
    }
    await manager.ensureBinary();
    if (verbose) {
        console.log(formatInfo('New version will be active on next CLIProxy command'));
    }
}
exports.installCliproxyVersion = installCliproxyVersion;
/** Fetch the latest CLIProxyAPI version from GitHub API */
async function fetchLatestCliproxyVersion(backend) {
    const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
    const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
    return result.latestVersion;
}
exports.fetchLatestCliproxyVersion = fetchLatestCliproxyVersion;
/** Check for CLIProxyAPI binary updates */
async function checkCliproxyUpdate(backend) {
    const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
    const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
    const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
    // Import isNewerVersion for stability check
    const { isNewerVersion } = await Promise.resolve().then(() => __importStar(require('./binary/version-checker')));
    const isStable = !isNewerVersion(result.currentVersion, platform_detector_1.CLIPROXY_MAX_STABLE_VERSION);
    const stabilityMessage = isStable
        ? undefined
        : `v${result.currentVersion} has known stability issues. Max stable: v${platform_detector_1.CLIPROXY_MAX_STABLE_VERSION}`;
    const backendLabel = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
    return {
        ...result,
        backend: effectiveBackend,
        backendLabel,
        isStable,
        maxStableVersion: platform_detector_1.CLIPROXY_MAX_STABLE_VERSION,
        stabilityMessage,
    };
}
exports.checkCliproxyUpdate = checkCliproxyUpdate;
exports.default = BinaryManager;
//# sourceMappingURL=binary-manager.js.map