"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 R = require("ramda");
const get_npm_tarball_url_1 = require("get-npm-tarball-url");
const fetch_1 = require("./fetch");
const logInstallStatus_1 = require("../logging/logInstallStatus");
const shrinkwrap_1 = require("../fs/shrinkwrap");
const depsToSpecs_1 = require("../depsToSpecs");
const getIsInstallable_1 = require("./getIsInstallable");
function installAll(ctx, specs, optionalDependencies, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const keypath = options.keypath || [];
        const specGroups = R.partition((spec) => !!spec.name && R.contains(spec.name, optionalDependencies), specs);
        const optionalDepSpecs = specGroups[0];
        const nonOptionalDepSpecs = specGroups[1];
        const installedPkgs = Array.prototype.concat.apply([], yield Promise.all([
            installMultiple(ctx, nonOptionalDepSpecs, Object.assign({}, options, { optional: false, keypath })),
            installMultiple(ctx, optionalDepSpecs, Object.assign({}, options, { optional: true, keypath })),
        ]));
        return installedPkgs;
    });
}
exports.default = installAll;
function installMultiple(ctx, specs, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const installedPkgs = (yield Promise.all(specs
            .map((spec) => __awaiter(this, void 0, void 0, function* () {
            const reference = options.resolvedDependencies &&
                options.resolvedDependencies[spec.name];
            const pkgShortId = reference && shrinkwrap_1.getPkgShortId(reference, spec.name);
            const dependencyShrinkwrap = pkgShortId && ctx.shrinkwrap.packages[pkgShortId];
            const pkgId = reference && shrinkwrap_1.getPkgId(reference, spec.name, ctx.shrinkwrap.registry);
            return yield install(spec, ctx, Object.assign({}, options, {
                pkgId,
                resolvedDependencies: dependencyShrinkwrap && dependencyShrinkwrap['dependencies'],
                shrinkwrapResolution: pkgShortId && dependencyShrinkwrap
                    ? dependencyShrToResolution(pkgShortId, dependencyShrinkwrap, options.registry)
                    : undefined,
            }));
        }))))
            .filter(pkg => pkg);
        return installedPkgs;
    });
}
function dependencyShrToResolution(pkgShortId, depShr, registry) {
    if (typeof depShr === 'string') {
        return {
            shasum: depShr,
            tarball: getTarball()
        };
    }
    if (typeof depShr.resolution === 'string') {
        return {
            shasum: depShr.resolution,
            tarball: getTarball(),
        };
    }
    if (!depShr.resolution.type && !depShr.resolution.tarball) {
        return Object.assign({}, depShr.resolution, {
            tarball: getTarball()
        });
    }
    return depShr.resolution;
    function getTarball() {
        const noPrefixPkgShortId = pkgShortId.substr(1);
        const divideAt = noPrefixPkgShortId.lastIndexOf('/');
        return get_npm_tarball_url_1.default(noPrefixPkgShortId.substr(0, divideAt), noPrefixPkgShortId.substr(divideAt + 1), { registry });
    }
}
function install(spec, ctx, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const keypath = options.keypath || [];
        const update = keypath.length <= options.depth;
        const registry = spec.scope && options.rawNpmConfig[`${spec.scope}:registry`] || options.registry;
        const dependentId = keypath[keypath.length - 1];
        const loggedPkg = {
            rawSpec: spec.rawSpec,
            name: spec.name,
            dependentId,
        };
        logInstallStatus_1.default({
            status: 'installing',
            pkg: loggedPkg,
        });
        const fetchedPkg = yield fetch_1.default(spec, {
            loggedPkg,
            update,
            fetchingLocker: ctx.fetchingLocker,
            registry,
            root: options.root,
            storePath: options.storePath,
            localRegistry: options.localRegistry,
            metaCache: options.metaCache,
            got: options.got,
            shrinkwrapResolution: options.shrinkwrapResolution,
            pkgId: options.pkgId,
            offline: options.offline,
        });
        const pkg = yield fetchedPkg.fetchingPkg;
        logInstallStatus_1.default({ status: 'downloaded_manifest', pkgId: fetchedPkg.id, pkgVersion: pkg.version });
        let dependencyIds;
        const isInstallable = options.isInstallable !== false && (options.force || (yield getIsInstallable_1.default(fetchedPkg.id, pkg, fetchedPkg, options)));
        if (!ctx.installed.has(fetchedPkg.id)) {
            ctx.installed.add(fetchedPkg.id);
            const dependencies = yield installDependencies(pkg, fetchedPkg.id, ctx, Object.assign({}, options, {
                root: fetchedPkg.srcPath,
                isInstallable,
            }));
            const shortId = shrinkwrap_1.pkgShortId(fetchedPkg.id, ctx.shrinkwrap.registry);
            ctx.shrinkwrap.packages[shortId] = toShrDependency(shortId, fetchedPkg.resolution, dependencies, ctx.shrinkwrap.registry);
            dependencyIds = dependencies.filter(dep => dep.isInstallable).map(dep => dep.id);
        }
        if (isInstallable && ctx.installationSequence.indexOf(fetchedPkg.id) === -1) {
            ctx.installationSequence.push(fetchedPkg.id);
        }
        const dependency = {
            id: fetchedPkg.id,
            resolution: fetchedPkg.resolution,
            srcPath: fetchedPkg.srcPath,
            optional: options.optional === true,
            pkg,
            isInstallable,
            dependencies: dependencyIds || [],
            fetchingFiles: fetchedPkg.fetchingFiles,
            path: fetchedPkg.path,
            specRaw: spec.raw,
        };
        addInstalledPkg(ctx.installs, dependency);
        logInstallStatus_1.default({ status: 'dependencies_installed', pkgId: fetchedPkg.id });
        return dependency;
    });
}
function toShrDependency(shortId, resolution, deps, registry) {
    const shrResolution = toShrResolution(shortId, resolution);
    if (deps.length) {
        return {
            resolution: shrResolution,
            dependencies: deps
                .reduce((resolutions, dep) => Object.assign(resolutions, {
                [dep.pkg.name]: shrinkwrap_1.pkgIdToRef(dep.id, dep.pkg.version, dep.resolution, registry)
            }), {})
        };
    }
    if (typeof shrResolution === 'string')
        return shrResolution;
    return {
        resolution: shrResolution
    };
}
function toShrResolution(shortId, resolution) {
    if (shortId.startsWith('/') && resolution.type === undefined && resolution.shasum) {
        return resolution.shasum;
    }
    return resolution;
}
function installDependencies(pkg, pkgId, ctx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const depsInstallOpts = Object.assign({}, opts, {
            keypath: opts.keypath.concat([pkgId]),
        });
        const bundledDeps = pkg.bundleDependencies || pkg.bundledDependencies || [];
        const filterDeps = getNotBundledDeps.bind(null, bundledDeps);
        const deps = depsToSpecs_1.default(filterDeps(Object.assign({}, pkg.optionalDependencies, pkg.dependencies)), opts.root);
        const optionalDeps = Object.keys(pkg.optionalDependencies || {});
        const installedDeps = yield installAll(ctx, deps, optionalDeps, depsInstallOpts);
        return installedDeps;
    });
}
function getNotBundledDeps(bundledDeps, deps) {
    return Object.keys(deps)
        .filter(depName => bundledDeps.indexOf(depName) === -1)
        .reduce((notBundledDeps, depName) => {
        notBundledDeps[depName] = deps[depName];
        return notBundledDeps;
    }, {});
}
function addInstalledPkg(installs, newPkg) {
    if (!newPkg.isInstallable)
        return;
    if (!installs[newPkg.id]) {
        installs[newPkg.id] = newPkg;
        return;
    }
    installs[newPkg.id].optional = installs[newPkg.id].optional && newPkg.optional;
    if (!installs[newPkg.id].dependencies.length) {
        installs[newPkg.id].dependencies = newPkg.dependencies;
    }
}
//# sourceMappingURL=installMultiple.js.map