"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_loggers_1 = require("@pnpm/core-loggers");
const filter_lockfile_1 = require("@pnpm/filter-lockfile");
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const logger_1 = require("@pnpm/logger");
const pkgid_to_filename_1 = require("@pnpm/pkgid-to-filename");
const read_modules_dir_1 = require("@pnpm/read-modules-dir");
const types_1 = require("@pnpm/types");
const rimraf = require("@zkochan/rimraf");
const path = require("path");
const R = require("ramda");
const removeDirectDependency_1 = require("./removeDirectDependency");
async function prune(importers, opts) {
    var _a;
    const wantedLockfile = filter_lockfile_1.default(opts.wantedLockfile, {
        include: opts.include,
        skipped: opts.skipped,
    });
    await Promise.all(importers.map(async ({ binsDir, id, modulesDir, pruneDirectDependencies, removePackages, rootDir }) => {
        const currentImporter = opts.currentLockfile.importers[id] || {};
        const currentPkgs = R.toPairs(mergeDependencies(currentImporter));
        const wantedPkgs = R.toPairs(mergeDependencies(wantedLockfile.importers[id]));
        const allCurrentPackages = new Set((pruneDirectDependencies || (removePackages === null || removePackages === void 0 ? void 0 : removePackages.length))
            ? (await read_modules_dir_1.default(modulesDir) || [])
            : []);
        const depsToRemove = new Set([
            ...(removePackages || []).filter((removePackage) => allCurrentPackages.has(removePackage)),
            ...R.difference(currentPkgs, wantedPkgs).map(([depName]) => depName),
        ]);
        if (pruneDirectDependencies) {
            if (allCurrentPackages.size > 0) {
                const newPkgsSet = new Set(wantedPkgs.map(([depName]) => depName));
                for (const currentPackage of Array.from(allCurrentPackages)) {
                    if (!newPkgsSet.has(currentPackage)) {
                        depsToRemove.add(currentPackage);
                    }
                }
            }
        }
        return Promise.all(Array.from(depsToRemove).map((depName) => {
            var _a, _b, _c;
            return removeDirectDependency_1.default({
                dependenciesField: ((_a = currentImporter.devDependencies) === null || _a === void 0 ? void 0 : _a[depName]) && 'devDependencies' ||
                    ((_b = currentImporter.optionalDependencies) === null || _b === void 0 ? void 0 : _b[depName]) && 'optionalDependencies' ||
                    ((_c = currentImporter.dependencies) === null || _c === void 0 ? void 0 : _c[depName]) && 'dependencies' ||
                    undefined,
                name: depName,
            }, {
                binsDir,
                dryRun: opts.dryRun,
                modulesDir,
                rootDir,
            });
        }));
    }));
    const selectedImporterIds = importers.map((importer) => importer.id).sort();
    // In case installation is done on a subset of importers,
    // we may only prune dependencies that are used only by that subset of importers.
    // Otherwise, we would break the node_modules.
    const currentPkgIdsByDepPaths = R.equals(selectedImporterIds, Object.keys(opts.wantedLockfile.importers))
        ? getPkgsDepPaths(opts.registries, (_a = opts.currentLockfile.packages) !== null && _a !== void 0 ? _a : {}, opts.skipped)
        : getPkgsDepPathsOwnedOnlyByImporters(selectedImporterIds, opts.registries, opts.currentLockfile, opts.include, opts.skipped);
    const wantedPkgIdsByDepPaths = getPkgsDepPaths(opts.registries, wantedLockfile.packages || {}, opts.skipped);
    const oldDepPaths = Object.keys(currentPkgIdsByDepPaths);
    const newDepPaths = Object.keys(wantedPkgIdsByDepPaths);
    const orphanDepPaths = R.difference(oldDepPaths, newDepPaths);
    const orphanPkgIds = new Set(R.props(orphanDepPaths, currentPkgIdsByDepPaths));
    core_loggers_1.statsLogger.debug({
        prefix: opts.lockfileDir,
        removed: orphanPkgIds.size,
    });
    if (!opts.dryRun) {
        if (orphanDepPaths.length) {
            if (opts.currentLockfile.packages &&
                opts.hoistedModulesDir &&
                opts.publicHoistedModulesDir) {
                const binsDir = path.join(opts.hoistedModulesDir, '.bin');
                const prefix = path.join(opts.virtualStoreDir, '../..');
                await Promise.all(orphanDepPaths.map(async (orphanDepPath) => {
                    if (opts.hoistedDependencies[orphanDepPath]) {
                        await Promise.all(Object.entries(opts.hoistedDependencies[orphanDepPath]).map(([alias, hoistType]) => {
                            const modulesDir = hoistType === 'public'
                                ? opts.publicHoistedModulesDir : opts.hoistedModulesDir;
                            return removeDirectDependency_1.default({
                                name: alias,
                            }, {
                                binsDir,
                                modulesDir,
                                muteLogs: true,
                                rootDir: prefix,
                            });
                        }));
                    }
                    delete opts.hoistedDependencies[orphanDepPath];
                }));
            }
            await Promise.all(orphanDepPaths.map(async (orphanDepPath) => {
                const pathToRemove = path.join(opts.virtualStoreDir, pkgid_to_filename_1.default(orphanDepPath, opts.lockfileDir));
                core_loggers_1.removalLogger.debug(pathToRemove);
                try {
                    await rimraf(pathToRemove);
                }
                catch (err) {
                    logger_1.default.warn({
                        error: err,
                        message: `Failed to remove "${pathToRemove}"`,
                        prefix: opts.lockfileDir,
                    });
                }
            }));
        }
    }
    return new Set(orphanDepPaths);
}
exports.default = prune;
function mergeDependencies(projectSnapshot) {
    return R.mergeAll(types_1.DEPENDENCIES_FIELDS.map((depType) => projectSnapshot[depType] || {}));
}
function getPkgsDepPaths(registries, packages, skipped) {
    const pkgIdsByDepPath = {};
    for (const depPath of Object.keys(packages)) {
        if (skipped.has(depPath))
            continue;
        pkgIdsByDepPath[depPath] = lockfile_utils_1.packageIdFromSnapshot(depPath, packages[depPath], registries);
    }
    return pkgIdsByDepPath;
}
function getPkgsDepPathsOwnedOnlyByImporters(importerIds, registries, lockfile, include, skipped) {
    const selected = filter_lockfile_1.filterLockfileByImporters(lockfile, importerIds, {
        failOnMissingDependencies: false,
        include,
        skipped,
    });
    const other = filter_lockfile_1.filterLockfileByImporters(lockfile, R.difference(Object.keys(lockfile.importers), importerIds), {
        failOnMissingDependencies: false,
        include,
        skipped,
    });
    const packagesOfSelectedOnly = R.pickAll(R.difference(Object.keys(selected.packages), Object.keys(other.packages)), selected.packages);
    return getPkgsDepPaths(registries, packagesOfSelectedOnly, skipped);
}
//# sourceMappingURL=prune.js.map