"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());
    });
};
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 requireJson_1 = require('../fs/requireJson');
const lock_1 = require('./lock');
const storeController_1 = require('../fs/storeController');
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));
    });
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = uninstallCmd;
function uninstallInContext(pkgsToUninstall, pkg, ctx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        pkg.dependencies = pkg.dependencies || {};
        // 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.store.packages[ctx.root] = ctx.store.packages[ctx.root] || {};
        ctx.store.packages[ctx.root].dependencies = ctx.store.packages[ctx.root].dependencies || {};
        const pkgIds = pkgsToUninstall
            .map(dep => ctx.store.packages[ctx.root].dependencies[dep])
            .filter(pkgId => !!pkgId);
        const uninstalledPkgs = tryUninstall(pkgIds.slice(), ctx.store, ctx.root);
        yield Promise.all(uninstalledPkgs.map(uninstalledPkg => removeBins(uninstalledPkg, ctx.storePath, ctx.root)));
        if (ctx.store.packages[ctx.root].dependencies) {
            pkgsToUninstall.forEach(dep => {
                delete ctx.store.packages[ctx.root].dependencies[dep];
            });
            if (!Object.keys(ctx.store.packages[ctx.root].dependencies).length) {
                delete ctx.store.packages[ctx.root].dependencies;
            }
        }
        yield Promise.all(uninstalledPkgs.map(pkgId => removePkgFromStore(pkgId, ctx.storePath)));
        yield storeController_1.save(ctx.storePath, ctx.store);
        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');
            yield removeDeps_1.default(pkgJsonPath, pkgsToUninstall, saveType);
        }
    });
}
exports.uninstallInContext = uninstallInContext;
function canBeUninstalled(pkgId, store, pkgPath) {
    return !store.packages[pkgId] || !store.packages[pkgId].dependents || !store.packages[pkgId].dependents.length ||
        store.packages[pkgId].dependents.length === 1 && store.packages[pkgId].dependents.indexOf(pkgPath) !== -1;
}
function tryUninstall(pkgIds, store, pkgPath) {
    const uninstalledPkgs = [];
    let numberOfUninstalls;
    do {
        numberOfUninstalls = 0;
        for (let i = 0; i < pkgIds.length;) {
            if (canBeUninstalled(pkgIds[i], store, pkgPath)) {
                const uninstalledPkg = pkgIds.splice(i, 1)[0];
                uninstalledPkgs.push(uninstalledPkg);
                const deps = store.packages[uninstalledPkg] && store.packages[uninstalledPkg].dependencies || {};
                const depIds = Object.keys(deps).map(depName => deps[depName]);
                delete store.packages[uninstalledPkg];
                depIds.forEach((dep) => removeDependency(dep, uninstalledPkg, store));
                Array.prototype.push.apply(uninstalledPkgs, tryUninstall(depIds, store, uninstalledPkg));
                numberOfUninstalls++;
                continue;
            }
            i++;
        }
    } while (numberOfUninstalls);
    return uninstalledPkgs;
}
exports.tryUninstall = tryUninstall;
function removeDependency(dependentPkgName, uninstalledPkg, store) {
    if (!store.packages[dependentPkgName].dependents)
        return;
    store.packages[dependentPkgName].dependents.splice(store.packages[dependentPkgName].dependents.indexOf(uninstalledPkg), 1);
    if (!store.packages[dependentPkgName].dependents.length) {
        delete store.packages[dependentPkgName].dependents;
    }
}
function removeBins(uninstalledPkg, store, root) {
    return __awaiter(this, void 0, void 0, function* () {
        const uninstalledPkgJson = yield requireJson_1.default(path.join(store, uninstalledPkg, '_/package.json'));
        const bins = binify_1.default(uninstalledPkgJson);
        return Promise.all(Object.keys(bins).map(bin => rimraf(path.join(root, 'node_modules/.bin', bin))));
    });
}
function removePkgFromStore(pkgId, store) {
    return rimraf(path.join(store, pkgId));
}
exports.removePkgFromStore = removePkgFromStore;
//# sourceMappingURL=uninstall.js.map