"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 fs = require("mz/fs");
const path = require("path");
const link_dir_1 = require("link-dir");
const symlink_dir_1 = require("symlink-dir");
const exists = require("path-exists");
const pnpm_logger_1 = require("pnpm-logger");
const R = require("ramda");
const pLimit = require("p-limit");
const linkBins_1 = require("./linkBins");
const resolvePeers_1 = require("./resolvePeers");
const logInstallStatus_1 = require("../logging/logInstallStatus");
function default_1(topPkgs, installedPkgs, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const pkgsToLinkMap = R.values(installedPkgs)
            .reduce((pkgsToLink, installedPkg) => {
            pkgsToLink[installedPkg.id] = {
                id: installedPkg.id,
                name: installedPkg.pkg.name,
                version: installedPkg.pkg.version,
                peerDependencies: installedPkg.pkg.peerDependencies || {},
                hasBundledDependencies: !!(installedPkg.pkg.bundledDependencies || installedPkg.pkg.bundleDependencies),
                fetchingFiles: installedPkg.fetchingFiles,
                localLocation: path.join(opts.baseNodeModules, `.${installedPkg.id}`),
                path: installedPkg.path,
                dependencies: installedPkg.dependencies,
            };
            return pkgsToLink;
        }, {});
        const topPkgIds = topPkgs.filter(pkg => pkg.isInstallable).map(pkg => pkg.id);
        const pkgsToLink = yield resolvePeers_1.default(pkgsToLinkMap, topPkgIds);
        const flatResolvedDeps = R.values(pkgsToLink).sort((a, b) => a.depth - b.depth);
        const deps = R.uniqBy(R.prop('hardlinkedLocation'), flatResolvedDeps);
        yield linkAllPkgs(deps, opts);
        const depsByModules = R.uniqBy(R.prop('modules'), flatResolvedDeps);
        yield linkAllIndependentModules(depsByModules, pkgsToLink);
        yield linkAllModules(deps, pkgsToLink);
        for (let pkg of flatResolvedDeps.filter(pkg => pkg.depth === 0)) {
            yield symlinkDependencyTo(pkg, opts.baseNodeModules);
            logInstallStatus_1.default({
                status: 'installed',
                pkgId: pkg.id,
            });
        }
        yield linkBins_1.default(opts.baseNodeModules, opts.bin);
        return pkgsToLink;
    });
}
exports.default = default_1;
const limitLinking = pLimit(16);
function linkAllPkgs(alldeps, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        return Promise.all(alldeps.map(pkg => limitLinking(() => linkPkg(pkg, opts))));
    });
}
function linkAllModules(pkgs, pkgMap) {
    return __awaiter(this, void 0, void 0, function* () {
        return Promise.all(pkgs.map(pkg => limitLinking(() => linkModules(pkg, pkgMap))));
    });
}
function linkAllIndependentModules(pkgs, pkgMap) {
    return __awaiter(this, void 0, void 0, function* () {
        return Promise.all(pkgs.map(pkg => limitLinking(() => linkIndependentModules(pkg, pkgMap))));
    });
}
function linkPkg(dependency, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const newlyFetched = yield dependency.fetchingFiles;
        const pkgJsonPath = path.join(dependency.hardlinkedLocation, 'package.json');
        if (newlyFetched || opts.force || !(yield exists(pkgJsonPath)) || !(yield pkgLinkedToStore(pkgJsonPath, dependency))) {
            yield link_dir_1.default(dependency.path, dependency.hardlinkedLocation);
        }
    });
}
function pkgLinkedToStore(pkgJsonPath, dependency) {
    return __awaiter(this, void 0, void 0, function* () {
        const pkgJsonPathInStore = path.join(dependency.path, 'package.json');
        if (yield isSameFile(pkgJsonPath, pkgJsonPathInStore))
            return true;
        pnpm_logger_1.default.info(`Relinking ${dependency.hardlinkedLocation} from the store`);
        return false;
    });
}
function isSameFile(file1, file2) {
    return __awaiter(this, void 0, void 0, function* () {
        const stats = yield Promise.all([fs.stat(file1), fs.stat(file2)]);
        return stats[0].ino === stats[1].ino;
    });
}
function linkIndependentModules(pkg, pkgMap) {
    return __awaiter(this, void 0, void 0, function* () {
        yield Promise.all(R.props(pkg.children, pkgMap)
            .map(child => symlinkDependencyTo(child, pkg.modules)));
    });
}
function linkModules(dependency, pkgMap) {
    return __awaiter(this, void 0, void 0, function* () {
        yield Promise.all(R.props(dependency.resolvedPeers, pkgMap)
            .map(peer => symlinkDependencyTo(peer, dependency.peerModules)));
        const binPath = path.join(dependency.hardlinkedLocation, 'node_modules', '.bin');
        yield linkBins_1.default(dependency.modules, binPath, dependency.name);
        if (dependency.peerModules) {
            yield linkBins_1.default(dependency.peerModules, binPath, dependency.name);
        }
        // link also the bundled dependencies` bins
        if (dependency.hasBundledDependencies) {
            const bundledModules = path.join(dependency.hardlinkedLocation, 'node_modules');
            yield linkBins_1.default(bundledModules, binPath);
        }
    });
}
function symlinkDependencyTo(dependency, dest) {
    dest = path.join(dest, dependency.name);
    return symlink_dir_1.default(dependency.hardlinkedLocation, dest);
}
//# sourceMappingURL=index.js.map