"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const error_1 = require("@pnpm/error");
const logger_1 = require("@pnpm/logger");
const getRegistryName = require("encode-registry");
const loadJsonFile = require("load-json-file");
const fs = require("mz/fs");
const pLimit = require("p-limit");
const path = require("path");
const pathTemp = require("path-temp");
const renameOverwrite = require("rename-overwrite");
const pickPackageFromMeta_1 = require("./pickPackageFromMeta");
const toRaw_1 = require("./toRaw");
/**
 * prevents simultaneous operations on the meta.json
 * otherwise it would cause EPERM exceptions
 */
const metafileOperationLimits = {};
exports.default = async (ctx, spec, opts) => {
    var _a;
    opts = opts || {};
    validatePackageName(spec.name);
    const cachedMeta = ctx.metaCache.get(spec.name);
    if (cachedMeta) {
        return {
            meta: cachedMeta,
            pickedPackage: pickPackageFromMeta_1.default(spec, opts.preferredVersionSelectors, cachedMeta),
        };
    }
    const registryName = getRegistryName(opts.registry);
    const pkgMirror = path.join(ctx.storeDir, ctx.metaDir, registryName, `${spec.name}.json`);
    const limit = metafileOperationLimits[pkgMirror] = metafileOperationLimits[pkgMirror] || pLimit(1);
    let metaCachedInStore;
    if (ctx.offline || ctx.preferOffline) {
        metaCachedInStore = await limit(() => loadMeta(pkgMirror));
        if (ctx.offline) {
            if (metaCachedInStore)
                return {
                    meta: metaCachedInStore,
                    pickedPackage: pickPackageFromMeta_1.default(spec, opts.preferredVersionSelectors, metaCachedInStore),
                };
            throw new error_1.default('NO_OFFLINE_META', `Failed to resolve ${toRaw_1.default(spec)} in package mirror ${pkgMirror}`);
        }
        if (metaCachedInStore) {
            const pickedPackage = pickPackageFromMeta_1.default(spec, opts.preferredVersionSelectors, metaCachedInStore);
            if (pickedPackage) {
                return {
                    meta: metaCachedInStore,
                    pickedPackage,
                };
            }
        }
    }
    if (spec.type === 'version') {
        metaCachedInStore = metaCachedInStore || await limit(() => loadMeta(pkgMirror));
        // use the cached meta only if it has the required package version
        // otherwise it is probably out of date
        if ((_a = metaCachedInStore === null || metaCachedInStore === void 0 ? void 0 : metaCachedInStore.versions) === null || _a === void 0 ? void 0 : _a[spec.fetchSpec]) {
            return {
                meta: metaCachedInStore,
                pickedPackage: metaCachedInStore.versions[spec.fetchSpec],
            };
        }
    }
    try {
        const meta = await ctx.fetch(spec.name, opts.registry, opts.authHeaderValue);
        meta.cachedAt = Date.now();
        // only save meta to cache, when it is fresh
        ctx.metaCache.set(spec.name, meta);
        if (!opts.dryRun) {
            // tslint:disable-next-line:no-floating-promises
            limit(async () => {
                try {
                    await saveMeta(pkgMirror, meta);
                }
                catch (err) {
                    // We don't care if this file was not written to the cache
                }
            });
        }
        return {
            meta,
            pickedPackage: pickPackageFromMeta_1.default(spec, opts.preferredVersionSelectors, meta),
        };
    }
    catch (err) {
        const meta = await loadMeta(pkgMirror); // TODO: add test for this usecase
        if (!meta)
            throw err;
        logger_1.default.error(err, err);
        logger_1.default.debug({ message: `Using cached meta from ${pkgMirror}` });
        return {
            meta,
            pickedPackage: pickPackageFromMeta_1.default(spec, opts.preferredVersionSelectors, meta),
        };
    }
};
async function loadMeta(pkgMirror) {
    try {
        return await loadJsonFile(pkgMirror);
    }
    catch (err) {
        return null;
    }
}
const createdDirs = new Set();
async function saveMeta(pkgMirror, meta) {
    const dir = path.dirname(pkgMirror);
    if (!createdDirs.has(dir)) {
        await fs.mkdir(dir, { recursive: true });
        createdDirs.add(dir);
    }
    const temp = pathTemp(dir);
    await fs.writeFile(temp, JSON.stringify(meta));
    await renameOverwrite(temp, pkgMirror);
}
function validatePackageName(pkgName) {
    if (pkgName.includes('/') && pkgName[0] !== '@') {
        throw new error_1.default('INVALID_PACKAGE_NAME', `Package name ${pkgName} is invalid, it should have a @scope`);
    }
}
//# sourceMappingURL=pickPackage.js.map