"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_loggers_1 = require("@pnpm/core-loggers");
const error_1 = require("@pnpm/error");
const logger_1 = require("@pnpm/logger");
const read_importers_context_1 = require("@pnpm/read-importers-context");
const types_1 = require("@pnpm/types");
const rimraf = require("@zkochan/rimraf");
const makeDir = require("make-dir");
const path = require("path");
const checkCompatibility_1 = require("./checkCompatibility");
const readLockfiles_1 = require("./readLockfiles");
async function getContext(importers, opts) {
    const importersContext = await read_importers_context_1.default(importers, opts.lockfileDirectory);
    if (importersContext.modules) {
        await validateNodeModules(importersContext.modules, importersContext.importers, {
            currentHoistPattern: importersContext.currentHoistPattern,
            force: opts.force,
            include: opts.include,
            lockfileDirectory: opts.lockfileDirectory,
            store: opts.store,
            forceIndependentLeaves: opts.forceIndependentLeaves,
            independentLeaves: opts.independentLeaves,
            forceHoistPattern: opts.forceHoistPattern,
            hoistPattern: opts.hoistPattern,
            forceShamefullyHoist: opts.forceShamefullyHoist,
            shamefullyHoist: opts.shamefullyHoist,
        });
    }
    await makeDir(opts.store);
    importers.forEach((importer) => {
        core_loggers_1.packageJsonLogger.debug({
            initial: importer.manifest,
            prefix: importer.prefix,
        });
    });
    if (opts.hooks && opts.hooks.readPackage) {
        importers = importers.map((importer) => (Object.assign(Object.assign({}, importer), { manifest: opts.hooks.readPackage(importer.manifest) })));
    }
    const virtualStoreDir = path.join(importersContext.rootModulesDir, '.pnpm');
    const extraBinPaths = [
        ...opts.extraBinPaths || []
    ];
    const shamefullyHoist = Boolean(typeof importersContext.shamefullyHoist === 'undefined' ? opts.shamefullyHoist : importersContext.shamefullyHoist);
    if (opts.hoistPattern && !shamefullyHoist) {
        extraBinPaths.unshift(path.join(virtualStoreDir, 'node_modules/.bin'));
    }
    const hoistedModulesDir = shamefullyHoist
        ? importersContext.rootModulesDir : path.join(virtualStoreDir, 'node_modules');
    const ctx = Object.assign({ extraBinPaths, hoistedAliases: importersContext.hoistedAliases, hoistedModulesDir, hoistPattern: typeof importersContext.hoist === 'boolean' ?
            importersContext.currentHoistPattern : opts.hoistPattern, importers: importersContext.importers, include: opts.include || importersContext.include, independentLeaves: Boolean(typeof importersContext.independentLeaves === 'undefined' ? opts.independentLeaves : importersContext.independentLeaves), lockfileDirectory: opts.lockfileDirectory, modulesFile: importersContext.modules, pendingBuilds: importersContext.pendingBuilds, registries: Object.assign(Object.assign({}, opts.registries), importersContext.registries), rootModulesDir: importersContext.rootModulesDir, shamefullyHoist, skipped: importersContext.skipped, storePath: opts.store, virtualStoreDir }, await readLockfiles_1.default({
        force: opts.force,
        forceSharedLockfile: opts.forceSharedLockfile,
        importers: importersContext.importers,
        lockfileDirectory: opts.lockfileDirectory,
        registry: opts.registries.default,
        useLockfile: opts.useLockfile,
    }));
    return ctx;
}
exports.default = getContext;
async function validateNodeModules(modules, importers, opts) {
    const rootImporter = importers.find(({ id }) => id === '.');
    if (opts.forceShamefullyHoist && modules.shamefullyHoist !== opts.shamefullyHoist) {
        if (opts.force && rootImporter) {
            await purgeModulesDirsOfImporter(rootImporter);
            return;
        }
        if (modules.shamefullyHoist) {
            throw new error_1.default('SHAMEFULLY_HOIST_WANTED', 'This "node_modules" folder was created using the --shamefully-flatten option.'
                + ' You must add that option, or else run "pnpm install --force" to recreate the "node_modules" folder.');
        }
        throw new error_1.default('SHAMEFULLY_HOIST_NOT_WANTED', 'This "node_modules" folder was created without the --shamefully-flatten option.'
            + ' You must remove that option, or else "pnpm install --force" to recreate the "node_modules" folder.');
    }
    if (opts.forceIndependentLeaves && Boolean(modules.independentLeaves) !== opts.independentLeaves) {
        if (opts.force) {
            // TODO: remove the node_modules in the lockfile directory
            await Promise.all(importers.map(purgeModulesDirsOfImporter));
            return;
        }
        if (modules.independentLeaves) {
            throw new error_1.default('INDEPENDENT_LEAVES_WANTED', 'This "node_modules" folder was created using the --independent-leaves option.'
                + ' You must add that option, or else run "pnpm install --force" to recreate the "node_modules" folder.');
        }
        throw new error_1.default('INDEPENDENT_LEAVES_NOT_WANTED', 'This "node_modules" folder was created without the --independent-leaves option.'
            + ' You must remove that option, or else "pnpm install --force" to recreate the "node_modules" folder.');
    }
    if (opts.forceHoistPattern && rootImporter) {
        try {
            if (opts.currentHoistPattern !== (opts.hoistPattern || undefined)) {
                if (opts.currentHoistPattern) {
                    throw new error_1.default('HOISTING_WANTED', 'This "node_modules" folder was created using the --hoist-pattern option.'
                        + ' You must add this option, or else add the --force option to recreate the "node_modules" folder.');
                }
                throw new error_1.default('HOISTING_NOT_WANTED', 'This "node_modules" folder was created without the --hoist-pattern option.'
                    + ' You must remove that option, or else add the --force option to recreate the "node_modules" folder.');
            }
        }
        catch (err) {
            if (!opts.force)
                throw err;
            await purgeModulesDirsOfImporter(rootImporter);
        }
    }
    await Promise.all(importers.map(async (importer) => {
        try {
            checkCompatibility_1.default(modules, { storePath: opts.store, modulesDir: importer.modulesDir });
            if (opts.lockfileDirectory !== importer.prefix && opts.include && modules.included) {
                for (const depsField of types_1.DEPENDENCIES_FIELDS) {
                    if (opts.include[depsField] !== modules.included[depsField]) {
                        throw new error_1.default('INCLUDED_DEPS_CONFLICT', `node_modules (at "${opts.lockfileDirectory}") was installed with ${stringifyIncludedDeps(modules.included)}. ` +
                            `Current install wants ${stringifyIncludedDeps(opts.include)}.`);
                    }
                }
            }
        }
        catch (err) {
            if (!opts.force)
                throw err;
            await purgeModulesDirsOfImporter(importer);
        }
    }));
}
async function purgeModulesDirsOfImporter(importer) {
    logger_1.default.info({
        message: `Recreating ${importer.modulesDir}`,
        prefix: importer.prefix,
    });
    try {
        await rimraf(importer.modulesDir);
    }
    catch (err) {
        if (err.code !== 'ENOENT')
            throw err;
    }
}
function stringifyIncludedDeps(included) {
    return types_1.DEPENDENCIES_FIELDS.filter((depsField) => included[depsField]).join(', ');
}
async function getContextForSingleImporter(manifest, opts) {
    const { currentHoistPattern, hoist, hoistedAliases, importers, include, independentLeaves, modules, pendingBuilds, registries, shamefullyHoist, skipped, rootModulesDir, } = await read_importers_context_1.default([
        {
            prefix: opts.prefix,
        },
    ], opts.lockfileDirectory);
    const storePath = opts.store;
    const importer = importers[0];
    const modulesDir = importer.modulesDir;
    const importerId = importer.id;
    if (modules) {
        await validateNodeModules(modules, importers, {
            currentHoistPattern,
            force: opts.force,
            include: opts.include,
            lockfileDirectory: opts.lockfileDirectory,
            store: opts.store,
            forceHoistPattern: opts.forceHoistPattern,
            hoistPattern: opts.hoistPattern,
            forceIndependentLeaves: opts.forceIndependentLeaves,
            independentLeaves: opts.independentLeaves,
            forceShamefullyHoist: opts.forceShamefullyHoist,
            shamefullyHoist: opts.shamefullyHoist,
        });
    }
    await makeDir(storePath);
    const virtualStoreDir = path.join(rootModulesDir, '.pnpm');
    const extraBinPaths = [
        ...opts.extraBinPaths || []
    ];
    const sHoist = Boolean(typeof shamefullyHoist === 'undefined' ? opts.shamefullyHoist : shamefullyHoist);
    if (opts.hoistPattern && !sHoist) {
        extraBinPaths.unshift(path.join(virtualStoreDir, 'node_modules/.bin'));
    }
    const hoistedModulesDir = sHoist
        ? rootModulesDir : path.join(virtualStoreDir, 'node_modules');
    const ctx = Object.assign({ extraBinPaths,
        hoistedAliases,
        hoistedModulesDir, hoistPattern: typeof hoist === 'boolean' ? currentHoistPattern : opts.hoistPattern, importerId, include: opts.include || include, independentLeaves: Boolean(typeof independentLeaves === 'undefined' ? opts.independentLeaves : independentLeaves), lockfileDirectory: opts.lockfileDirectory, manifest: opts.hooks && opts.hooks.readPackage ? opts.hooks.readPackage(manifest) : manifest, modulesDir, modulesFile: modules, pendingBuilds, prefix: opts.prefix, registries: Object.assign(Object.assign({}, opts.registries), registries), rootModulesDir, shamefullyHoist: sHoist, skipped,
        storePath,
        virtualStoreDir }, await readLockfiles_1.default({
        force: opts.force,
        forceSharedLockfile: opts.forceSharedLockfile,
        importers: [{ id: importerId, prefix: opts.prefix }],
        lockfileDirectory: opts.lockfileDirectory,
        registry: opts.registries.default,
        useLockfile: opts.useLockfile,
    }));
    core_loggers_1.packageJsonLogger.debug({
        initial: manifest,
        prefix: opts.prefix,
    });
    return ctx;
}
exports.getContextForSingleImporter = getContextForSingleImporter;
