"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 logger_1 = require("@pnpm/logger");
const find_packages_1 = require("find-packages");
const graphSequencer = require("graph-sequencer");
const pLimit = require("p-limit");
const path = require("path");
const pkgs_graph_1 = require("pkgs-graph");
const supi_1 = require("supi");
const createStoreController_1 = require("../createStoreController");
const requireHooks_1 = require("../requireHooks");
const supportedRecursiveCommands = new Set([
    'install',
    'i',
    'update',
    'up',
    'upgrade',
    'link',
    'ln',
    'dislink',
]);
exports.default = (input, opts) => __awaiter(this, void 0, void 0, function* () {
    let concurrency = 4;
    if (!isNaN(parseInt(input[0], 10))) {
        concurrency = parseInt(input.shift(), 10);
    }
    if (concurrency < 1) {
        throw new Error('Concurrency should be at least 1');
    }
    const cmd = input.shift();
    if (cmd && !supportedRecursiveCommands.has(cmd)) {
        throw new Error('Unsupported recursive command');
    }
    logger_1.default.warn('The recursive command is an experimental feature. Breaking changes may happen in non-major versions.');
    if (cmd === 'update' || cmd === 'up' || cmd === 'upgrade') {
        opts = Object.assign({}, opts, { update: true });
    }
    const pkgs = yield find_packages_1.default(process.cwd(), {
        ignore: [
            '**/node_modules/**',
            '**/bower_components/**',
        ],
    });
    const pkgGraphResult = pkgs_graph_1.default(pkgs);
    const store = yield createStoreController_1.default(opts);
    if (cmd === 'link' || cmd === 'ln') {
        yield linkPackages(pkgGraphResult.graph, store.ctrl, store.path);
    }
    const graph = new Map(Object.keys(pkgGraphResult.graph).map((pkgPath) => [pkgPath, pkgGraphResult.graph[pkgPath].dependencies]));
    const graphSequencerResult = graphSequencer({
        graph,
        groups: [Object.keys(pkgGraphResult.graph)],
    });
    const chunks = graphSequencerResult.chunks;
    // It is enough to save the store.json file once,
    // once all installations are done.
    // That's why saveState that is passed to the install engine
    // does nothing.
    const saveState = store.ctrl.saveState;
    const storeController = Object.assign({}, store.ctrl, { saveState: () => __awaiter(this, void 0, void 0, function* () { return undefined; }) });
    const installOpts = Object.assign(opts, {
        store: store.path,
        storeController,
    });
    const limitInstallation = pLimit(concurrency);
    const action = cmd === 'dislink' ? supi_1.unlink : supi_1.install;
    for (const chunk of chunks) {
        yield Promise.all(chunk.map((prefix) => limitInstallation(() => __awaiter(this, void 0, void 0, function* () {
            const hooks = opts.ignorePnpmfile ? {} : requireHooks_1.default(prefix);
            try {
                return yield action(Object.assign({}, installOpts, { bin: path.join(prefix, 'node_modules', '.bin'), hooks,
                    prefix,
                    storeController }));
            }
            catch (err) {
                err['prefix'] = prefix; // tslint:disable-line:no-string-literal
                return err;
            }
        }))));
    }
    yield saveState();
});
function linkPackages(graph, storeController, store) {
    const limitLinking = pLimit(12);
    const linkOpts = { skipInstall: true, store, storeController };
    return Promise.all(Object.keys(graph)
        .map((pkgPath) => Promise.all((graph[pkgPath].dependencies || [])
        .map((depPath) => limitLinking(() => supi_1.link(depPath, pkgPath, linkOpts))))));
}
//# sourceMappingURL=recursive.js.map