"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const rimraf = require("rimraf-then");
const path = require("path");
const getContext_1 = require("./getContext");
const getSaveType_1 = require("../getSaveType");
const removeDeps_1 = require("../removeDeps");
const binify_1 = require("../binify");
const extendOptions_1 = require("./extendOptions");
const readPkg_1 = require("../fs/readPkg");
const lock_1 = require("./lock");
const graphController_1 = require("../fs/graphController");
const shrinkwrap_1 = require("../fs/shrinkwrap");
function uninstallCmd(pkgsToUninstall, maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const opts = extendOptions_1.default(maybeOpts);
        const ctx = yield getContext_1.default(opts);
        if (!ctx.pkg) {
            throw new Error('No package.json found - cannot uninstall');
        }
        const pkg = ctx.pkg;
        return lock_1.default(ctx.storePath, () => uninstallInContext(pkgsToUninstall, pkg, ctx, opts), { stale: opts.lockStaleDuration });
    });
}
exports.default = uninstallCmd;
function uninstallInContext(pkgsToUninstall, pkg, ctx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        pkg.dependencies = pkg.dependencies || {};
        // for backward compatibility
        const entry = ctx.graph[ctx.root] ? ctx.root : graphController_1.GRAPH_ENTRY;
        // this is OK. The store might not have records for the package
        // maybe it was cloned, `pnpm install` was not executed
        // and remove is done on a package with no dependencies installed
        ctx.graph[entry] = ctx.graph[entry] || {};
        ctx.graph[entry].dependencies = ctx.graph[entry].dependencies || {};
        const pkgIds = pkgsToUninstall
            .map(dep => ctx.graph[entry].dependencies[dep])
            .filter(pkgId => !!pkgId);
        const uninstalledPkgs = tryUninstall(pkgIds.slice(), ctx.graph, entry);
        yield Promise.all(uninstalledPkgs.map(uninstalledPkg => removeBins(uninstalledPkg, ctx.storePath, ctx.root)));
        if (ctx.graph[entry].dependencies) {
            pkgsToUninstall.forEach(dep => {
                delete ctx.graph[entry].dependencies[dep];
            });
            if (!Object.keys(ctx.graph[entry].dependencies).length) {
                delete ctx.graph[entry].dependencies;
            }
        }
        yield Promise.all(uninstalledPkgs.map(pkgId => removePkgFromStore(pkgId, ctx.storePath)));
        yield graphController_1.save(path.join(ctx.root, 'node_modules'), ctx.graph);
        yield Promise.all(pkgsToUninstall.map(dep => rimraf(path.join(ctx.root, 'node_modules', dep))));
        const saveType = getSaveType_1.default(opts);
        if (saveType) {
            const pkgJsonPath = path.join(ctx.root, 'package.json');
            const pkg = yield removeDeps_1.default(pkgJsonPath, pkgsToUninstall, saveType);
            for (let depName in ctx.shrinkwrap.dependencies) {
                if (!isDependentOn(pkg, depName)) {
                    delete ctx.shrinkwrap.dependencies[depName];
                }
            }
            yield shrinkwrap_1.save(ctx.root, ctx.shrinkwrap);
        }
    });
}
exports.uninstallInContext = uninstallInContext;
function isDependentOn(pkg, depName) {
    return [
        'dependencies',
        'devDependencies',
        'optionalDependencies',
    ]
        .some(deptype => pkg[deptype] && pkg[deptype][depName]);
}
function canBeUninstalled(pkgId, graph, pkgPath) {
    return !graph[pkgId] || !graph[pkgId].dependents || !graph[pkgId].dependents.length ||
        graph[pkgId].dependents.length === 1 && graph[pkgId].dependents.indexOf(pkgPath) !== -1;
}
function tryUninstall(pkgIds, graph, pkgPath) {
    const uninstalledPkgs = [];
    let numberOfUninstalls;
    do {
        numberOfUninstalls = 0;
        for (let i = 0; i < pkgIds.length;) {
            if (canBeUninstalled(pkgIds[i], graph, pkgPath)) {
                const uninstalledPkg = pkgIds.splice(i, 1)[0];
                uninstalledPkgs.push(uninstalledPkg);
                const deps = graph[uninstalledPkg] && graph[uninstalledPkg].dependencies || {};
                const depIds = Object.keys(deps).map(depName => deps[depName]);
                delete graph[uninstalledPkg];
                depIds.forEach((dep) => removeDependency(dep, uninstalledPkg, graph));
                Array.prototype.push.apply(uninstalledPkgs, tryUninstall(depIds, graph, uninstalledPkg));
                numberOfUninstalls++;
                continue;
            }
            i++;
        }
    } while (numberOfUninstalls);
    return uninstalledPkgs;
}
exports.tryUninstall = tryUninstall;
function removeDependency(dependentPkgName, uninstalledPkg, graph) {
    if (!graph[dependentPkgName].dependents)
        return;
    graph[dependentPkgName].dependents.splice(graph[dependentPkgName].dependents.indexOf(uninstalledPkg), 1);
    if (!graph[dependentPkgName].dependents.length) {
        delete graph[dependentPkgName].dependents;
    }
}
function removeBins(uninstalledPkg, store, root) {
    return __awaiter(this, void 0, void 0, function* () {
        const uninstalledPkgPath = path.join(store, uninstalledPkg);
        const uninstalledPkgJson = yield readPkg_1.default(uninstalledPkgPath);
        const cmds = yield binify_1.default(uninstalledPkgJson, uninstalledPkgPath);
        return Promise.all(cmds.map(cmd => rimraf(path.join(root, 'node_modules', '.bin', cmd.name))));
    });
}
function removePkgFromStore(pkgId, store) {
    return rimraf(path.join(store, pkgId));
}
exports.removePkgFromStore = removePkgFromStore;
//# sourceMappingURL=uninstall.js.map