"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_loggers_1 = require("@pnpm/core-loggers");
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const logger_1 = require("@pnpm/logger");
const read_modules_dir_1 = require("@pnpm/read-modules-dir");
const types_1 = require("@pnpm/types");
const dp = require("dependency-path");
const vacuumCB = require("fs-vacuum");
const path = require("path");
const R = require("ramda");
const promisify = require("util.promisify");
const removeDirectDependency_1 = require("./removeDirectDependency");
const vacuum = promisify(vacuumCB);
async function prune(opts) {
    await Promise.all(opts.importers.map(async (importer) => {
        const oldLockfileImporter = opts.oldLockfile.importers[importer.id] || {};
        const oldPkgs = R.toPairs(mergeDependencies(oldLockfileImporter));
        const newPkgs = R.toPairs(mergeDependencies(opts.newLockfile.importers[importer.id]));
        const allCurrentPackages = new Set((importer.pruneDirectDependencies || importer.removePackages && importer.removePackages.length)
            ? (await read_modules_dir_1.default(importer.modulesDir) || [])
            : []);
        const depsToRemove = new Set([
            ...(importer.removePackages || []).filter((removePackage) => allCurrentPackages.has(removePackage)),
            ...R.difference(oldPkgs, newPkgs).map(([depName]) => depName),
        ]);
        if (importer.pruneDirectDependencies) {
            if (allCurrentPackages.size > 0) {
                const newPkgsSet = new Set(newPkgs.map(([depName]) => depName));
                for (const currentPackage of Array.from(allCurrentPackages)) {
                    if (!newPkgsSet.has(currentPackage)) {
                        depsToRemove.add(currentPackage);
                    }
                }
            }
        }
        const { bin, modulesDir, prefix } = importer;
        return Promise.all(Array.from(depsToRemove).map((depName) => {
            return removeDirectDependency_1.default({
                dependenciesField: oldLockfileImporter.devDependencies && oldLockfileImporter.devDependencies[depName] && 'devDependencies' ||
                    oldLockfileImporter.optionalDependencies && oldLockfileImporter.optionalDependencies[depName] && 'optionalDependencies' ||
                    oldLockfileImporter.dependencies && oldLockfileImporter.dependencies[depName] && 'dependencies' ||
                    undefined,
                name: depName,
            }, {
                bin,
                dryRun: opts.dryRun,
                modulesDir,
                prefix,
            });
        }));
    }));
    const oldPkgIdsByDepPaths = getPkgsDepPaths(opts.registries, opts.oldLockfile.packages || {});
    const newPkgIdsByDepPaths = getPkgsDepPaths(opts.registries, opts.newLockfile.packages || {});
    const oldDepPaths = Object.keys(oldPkgIdsByDepPaths);
    const newDepPaths = Object.keys(newPkgIdsByDepPaths);
    const orphanDepPaths = R.difference(oldDepPaths, newDepPaths);
    const orphanPkgIds = new Set(R.props(orphanDepPaths, oldPkgIdsByDepPaths));
    core_loggers_1.statsLogger.debug({
        prefix: opts.lockfileDirectory,
        removed: orphanPkgIds.size,
    });
    if (!opts.dryRun) {
        if (orphanDepPaths.length) {
            if (opts.oldLockfile.packages) {
                await Promise.all(opts.importers.filter((importer) => importer.shamefullyFlatten).map((importer) => {
                    const { bin, hoistedAliases, modulesDir, prefix } = importer;
                    return Promise.all(orphanDepPaths.map(async (orphanDepPath) => {
                        if (hoistedAliases[orphanDepPath]) {
                            await Promise.all(hoistedAliases[orphanDepPath].map((alias) => {
                                return removeDirectDependency_1.default({
                                    name: alias,
                                }, {
                                    bin,
                                    modulesDir,
                                    muteLogs: true,
                                    prefix,
                                });
                            }));
                        }
                        delete hoistedAliases[orphanDepPath];
                    }));
                }));
            }
            await Promise.all(orphanDepPaths.map(async (orphanDepPath) => {
                const pathToRemove = path.join(opts.virtualStoreDir, `.${orphanDepPath}`, 'node_modules');
                core_loggers_1.removalLogger.debug(pathToRemove);
                try {
                    await vacuum(pathToRemove, {
                        base: opts.virtualStoreDir,
                        purge: true,
                    });
                }
                catch (err) {
                    logger_1.default.warn({
                        error: err,
                        message: `Failed to remove "${pathToRemove}"`,
                        prefix: opts.lockfileDirectory,
                    });
                }
            }));
        }
        const addedDepPaths = R.difference(newDepPaths, oldDepPaths);
        const addedPkgIds = new Set(R.props(addedDepPaths, newPkgIdsByDepPaths));
        await opts.storeController.updateConnections(path.dirname(opts.virtualStoreDir), {
            addDependencies: Array.from(addedPkgIds),
            prune: opts.pruneStore || false,
            removeDependencies: Array.from(orphanPkgIds),
        });
        await opts.storeController.saveState();
    }
    return new Set(orphanDepPaths);
}
exports.default = prune;
function mergeDependencies(lockfileImporter) {
    return R.mergeAll(types_1.DEPENDENCIES_FIELDS.map((depType) => lockfileImporter[depType]));
}
function getPkgsDepPaths(registries, packages) {
    const pkgIdsByDepPath = {};
    for (const relDepPath of Object.keys(packages)) {
        const depPath = dp.resolve(registries, relDepPath);
        pkgIdsByDepPath[depPath] = lockfile_utils_1.packageIdFromSnapshot(relDepPath, packages[relDepPath], registries);
    }
    return pkgIdsByDepPath;
}
//# sourceMappingURL=prune.js.map