"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 path = require('path');
const fetch_1 = require('./fetch');
const linkBins_1 = require('./linkBins');
const memoize_1 = require('../memoize');
const symlinkToModules_1 = require('./symlinkToModules');
const mkdirp_1 = require('../fs/mkdirp');
const installChecks = require('pnpm-install-checks');
const pnpmPkgJson_1 = require('../pnpmPkgJson');
function installAll(ctx, dependencies, optionalDependencies, modules, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const nonOptionalDependencies = Object.keys(dependencies)
            .reduce((deps, depName) => {
            if (!optionalDependencies[depName]) {
                deps[depName] = dependencies[depName];
            }
            return deps;
        }, {});
        const installedPkgs = Array.prototype.concat.apply([], yield Promise.all([
            installMultiple(ctx, nonOptionalDependencies, modules, Object.assign({}, options, { optional: false })),
            installMultiple(ctx, optionalDependencies, modules, Object.assign({}, options, { optional: true })),
        ]));
        if (options.fetchingFiles) {
            yield options.fetchingFiles;
        }
        yield mkdirp_1.default(modules);
        yield Promise.all(installedPkgs
            .filter(subdep => !subdep.fromCache)
            .map(subdep => symlinkToModules_1.default(subdep.path, modules)));
        yield linkBins_1.default(modules);
        return installedPkgs;
    });
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = installAll;
function installMultiple(ctx, pkgsMap, modules, options) {
    return __awaiter(this, void 0, void 0, function* () {
        pkgsMap = pkgsMap || {};
        const pkgs = Object.keys(pkgsMap).map(pkgName => getRawSpec(pkgName, pkgsMap[pkgName]));
        ctx.store.packages = ctx.store.packages || {};
        const installedPkgs = (yield Promise.all(pkgs.map(function (pkgRawSpec) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    return yield install(pkgRawSpec, modules, ctx, options);
                }
                catch (err) {
                    if (options.optional) {
                        console.log(`Skipping failed optional dependency ${pkgRawSpec}:`);
                        console.log(err.message || err);
                        return null; // is it OK to return null?  
                    }
                    throw err;
                }
            });
        })))
            .filter(pkg => pkg);
        return installedPkgs;
    });
}
function install(pkgRawSpec, modules, ctx, options) {
    return __awaiter(this, void 0, void 0, function* () {
        options.keypath = options.keypath || [];
        const fetchedPkg = yield fetch_1.default(ctx, pkgRawSpec, modules, options);
        const pkg = yield fetchedPkg.fetchingPkg;
        if (!options.force) {
            yield isInstallable(pkg, fetchedPkg, options);
        }
        const dependency = Object.assign({}, fetchedPkg, {
            keypath: options.keypath,
            dependencies: [],
            optional: options.optional === true,
            pkg,
        });
        addInstalledPkg(ctx.installs, dependency);
        ctx.store.packages[options.dependent] = ctx.store.packages[options.dependent] || {};
        ctx.store.packages[options.dependent].dependencies = ctx.store.packages[options.dependent].dependencies || {};
        // NOTE: the current install implementation
        // does not return enough info for packages that were already installed
        if (dependency.fromCache || options.keypath.indexOf(dependency.id) !== -1) {
            yield dependency.fetchingFiles;
            return dependency;
        }
        ctx.store.packages[options.dependent].dependencies[pkg.name] = dependency.id;
        ctx.store.packages[dependency.id] = ctx.store.packages[dependency.id] || {};
        ctx.store.packages[dependency.id].dependents = ctx.store.packages[dependency.id].dependents || [];
        if (ctx.store.packages[dependency.id].dependents.indexOf(options.dependent) === -1) {
            ctx.store.packages[dependency.id].dependents.push(options.dependent);
        }
        // when a package was already installed, update the subdependencies only to the specified depth.
        if (!dependency.justFetched && options.keypath.length >= options.depth) {
            yield dependency.fetchingFiles;
            return dependency;
        }
        // greedy installation does not work with bundled dependencies
        if (pkg.bundleDependencies && pkg.bundleDependencies.length || pkg.bundledDependencies && pkg.bundledDependencies.length) {
            yield dependency.fetchingFiles;
        }
        dependency.dependencies = yield memoize_1.default(ctx.installLocks, dependency.id, () => {
            return installDependencies(pkg, dependency, ctx, options);
        });
        yield dependency.fetchingFiles;
        return dependency;
    });
}
function isInstallable(pkg, fetchedPkg, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const warn = (yield installChecks.checkPlatform(pkg)) || (yield installChecks.checkEngine(pkg, {
            pnpmVersion: pnpmPkgJson_1.default.version,
            nodeVersion: options.nodeVersion
        }));
        if (!warn)
            return;
        switch (warn.code) {
            case 'EBADPLATFORM':
                console.warn(`Unsupported system. Skipping dependency ${fetchedPkg.id}`);
                break;
            case 'ENOTSUP':
                console.warn(warn);
                break;
        }
        if (options.engineStrict || options.optional) {
            yield fetchedPkg.abort();
            throw warn;
        }
    });
}
function installDependencies(pkg, dependency, ctx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const depsInstallOpts = Object.assign({}, opts, {
            keypath: (opts.keypath || []).concat([dependency.id]),
            dependent: dependency.id,
            root: dependency.srcPath,
            fetchingFiles: dependency.fetchingFiles,
        });
        const modules = path.join(dependency.path, 'node_modules');
        const installedDeps = yield installAll(ctx, pkg.dependencies || {}, pkg.optionalDependencies || {}, modules, depsInstallOpts);
        ctx.piq = ctx.piq || [];
        ctx.piq.push({
            path: dependency.path,
            pkgId: dependency.id
        });
        return installedDeps;
    });
}
function addInstalledPkg(installs, newPkg) {
    if (!installs[newPkg.id]) {
        installs[newPkg.id] = newPkg;
        return;
    }
    installs[newPkg.id].optional = installs[newPkg.id].optional && newPkg.optional;
}
function getRawSpec(name, version) {
    return version === '*' ? name : `${name}@${version}`;
}
//# sourceMappingURL=installMultiple.js.map