"use strict";
/**
 * Binary Installer
 * Handles downloading, verifying, and extracting binary.
 */
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.getBinaryInfo = exports.isBinaryInstalled = exports.getBinaryPath = exports.deleteBinary = exports.downloadAndInstall = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const platform_detector_1 = require("../binary/platform-detector");
const downloader_1 = require("./downloader");
const verifier_1 = require("./verifier");
const extractor_1 = require("./extractor");
const version_cache_1 = require("./version-cache");
const progress_indicator_1 = require("../../utils/progress-indicator");
const ui_1 = require("../../utils/ui");
/**
 * Download and install the binary
 */
async function downloadAndInstall(config, verbose = false) {
    const backend = config.backend ?? platform_detector_1.DEFAULT_BACKEND;
    const platform = (0, platform_detector_1.detectPlatform)(config.version, backend);
    const downloadUrl = (0, platform_detector_1.getDownloadUrl)(config.version, backend);
    const checksumsUrl = (0, platform_detector_1.getChecksumsUrl)(config.version, backend);
    const backendLabel = backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
    fs.mkdirSync(config.binPath, { recursive: true });
    // Delete existing binary before install to prevent mismatched binaries.
    // Abort if binary is currently running (ETXTBSY) — cannot replace in-use binary.
    // Happens in Docker when dashboard tries to update while bootstrap's instance is active.
    const existingBinary = path.join(config.binPath, (0, platform_detector_1.getExecutableName)(backend));
    if (fs.existsSync(existingBinary)) {
        try {
            fs.unlinkSync(existingBinary);
            if (verbose)
                console.error(`[cliproxy] Removed existing binary: ${existingBinary}`);
        }
        catch (error) {
            const code = error instanceof Error && 'code' in error ? error.code : '';
            // ETXTBSY: Linux-specific error when unlinking a running executable.
            // EBUSY on Windows may mean something different (mount point, etc.),
            // so only treat ETXTBSY as "binary in use" to avoid misleading messages.
            if (code === 'ETXTBSY') {
                if (verbose)
                    console.error(`[cliproxy] Binary is running, cannot replace: ${existingBinary}`);
                throw new Error('CLIProxy binary is currently running and cannot be replaced. Restart the container to apply the update.');
            }
            throw error;
        }
    }
    const archivePath = path.join(config.binPath, `cliproxy-archive.${platform.extension}`);
    const spinner = new progress_indicator_1.ProgressIndicator(`Downloading ${backendLabel} v${config.version}`);
    spinner.start();
    try {
        const result = await (0, downloader_1.downloadWithRetry)(downloadUrl, archivePath, {
            maxRetries: config.maxRetries,
            verbose,
        });
        if (!result.success) {
            spinner.fail('Download failed');
            throw new Error(result.error || 'Download failed after retries');
        }
        spinner.update('Verifying checksum');
        const checksumResult = await (0, verifier_1.verifyChecksum)(archivePath, platform.binaryName, checksumsUrl, verbose);
        if (!checksumResult.valid) {
            spinner.fail('Checksum mismatch');
            fs.unlinkSync(archivePath);
            throw new Error(`Checksum mismatch for ${platform.binaryName}\nExpected: ${checksumResult.expected}\n` +
                `Actual:   ${checksumResult.actual}\n\nManual download: ${downloadUrl}`);
        }
        spinner.update('Extracting binary');
        await (0, extractor_1.extractArchive)(archivePath, config.binPath, platform.extension, verbose, backend);
        spinner.succeed(`${backendLabel} ready`);
        fs.unlinkSync(archivePath);
        const binaryPath = path.join(config.binPath, (0, platform_detector_1.getExecutableName)(backend));
        if (platform.os !== 'windows' && fs.existsSync(binaryPath)) {
            fs.chmodSync(binaryPath, 0o755);
            if (verbose)
                console.error(`[cliproxy] Set executable permissions: ${binaryPath}`);
        }
        (0, version_cache_1.writeInstalledVersion)(config.binPath, config.version);
        console.log((0, ui_1.ok)(`${backendLabel} v${config.version} installed successfully`));
    }
    catch (error) {
        spinner.fail('Installation failed');
        throw error;
    }
}
exports.downloadAndInstall = downloadAndInstall;
/** Delete binary (for cleanup or reinstall) */
function deleteBinary(binPath, verbose = false, backend) {
    const effectiveBackend = backend ?? platform_detector_1.DEFAULT_BACKEND;
    const binaryPath = path.join(binPath, (0, platform_detector_1.getExecutableName)(effectiveBackend));
    if (fs.existsSync(binaryPath)) {
        try {
            fs.unlinkSync(binaryPath);
            if (verbose)
                console.error(`[cliproxy] Deleted: ${binaryPath}`);
        }
        catch (error) {
            const code = error instanceof Error && 'code' in error ? error.code : '';
            if (code === 'ETXTBSY') {
                throw new Error('CLIProxy binary is currently running and cannot be deleted.');
            }
            throw error;
        }
    }
}
exports.deleteBinary = deleteBinary;
/** Get binary path */
function getBinaryPath(binPath, backend) {
    const effectiveBackend = backend ?? platform_detector_1.DEFAULT_BACKEND;
    return path.join(binPath, (0, platform_detector_1.getExecutableName)(effectiveBackend));
}
exports.getBinaryPath = getBinaryPath;
/** Check if binary exists */
function isBinaryInstalled(binPath, backend) {
    return fs.existsSync(getBinaryPath(binPath, backend));
}
exports.isBinaryInstalled = isBinaryInstalled;
/** Get binary info if installed */
async function getBinaryInfo(binPath, version, backend) {
    const effectiveBackend = backend ?? platform_detector_1.DEFAULT_BACKEND;
    const binaryPath = getBinaryPath(binPath, effectiveBackend);
    if (!fs.existsSync(binaryPath))
        return null;
    const platform = (0, platform_detector_1.detectPlatform)(undefined, effectiveBackend);
    const checksum = await (0, verifier_1.computeChecksum)(binaryPath);
    return { path: binaryPath, version, platform, checksum };
}
exports.getBinaryInfo = getBinaryInfo;
//# sourceMappingURL=installer.js.map