"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = require("@pnpm/logger");
const pkgid_to_filename_1 = require("@pnpm/pkgid-to-filename");
const utils_1 = require("@pnpm/utils");
const common_tags_1 = require("common-tags");
const crypto = require("crypto");
const importFrom = require("import-from");
const path = require("path");
const R = require("ramda");
const semver = require("semver");
const errorTypes_1 = require("../errorTypes");
function default_1(opts) {
    const depGraph = {};
    const absolutePathsByNodeId = {};
    for (const importer of opts.importers) {
        const { directNodeIdsByAlias, usesExternalShrinkwrap, topParents, prefix } = importer;
        const pkgsByName = Object.assign(R.fromPairs(topParents.map((parent) => [
            parent.name,
            {
                depth: 0,
                version: parent.version,
            },
        ])), toPkgByName(R
            .keys(directNodeIdsByAlias)
            .map((alias) => ({
            alias,
            node: opts.dependenciesTree[directNodeIdsByAlias[alias]],
            nodeId: directNodeIdsByAlias[alias],
        }))));
        resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
            absolutePathsByNodeId,
            dependenciesTree: opts.dependenciesTree,
            depGraph,
            independentLeaves: opts.independentLeaves,
            prefix,
            purePkgs: new Set(),
            strictPeerDependencies: opts.strictPeerDependencies,
            usesExternalShrinkwrap,
            virtualStoreDir: opts.virtualStoreDir,
        });
    }
    R.values(depGraph).forEach((node) => {
        node.children = R.keys(node.children).reduce((acc, alias) => {
            acc[alias] = absolutePathsByNodeId[node.children[alias]];
            return acc;
        }, {});
    });
    const importersDirectAbsolutePathsByAlias = {};
    for (const importer of opts.importers) {
        const { directNodeIdsByAlias } = importer;
        importersDirectAbsolutePathsByAlias[importer.id] = R.keys(directNodeIdsByAlias).reduce((rootAbsolutePathsByAlias, alias) => {
            rootAbsolutePathsByAlias[alias] = absolutePathsByNodeId[directNodeIdsByAlias[alias]];
            return rootAbsolutePathsByAlias;
        }, {});
    }
    return {
        depGraph,
        importersDirectAbsolutePathsByAlias,
    };
}
exports.default = default_1;
function resolvePeersOfNode(nodeId, parentParentPkgs, ctx) {
    const node = ctx.dependenciesTree[nodeId];
    if (ctx.purePkgs.has(node.resolvedPackage.id) && ctx.depGraph[node.resolvedPackage.id].depth <= node.depth) {
        ctx.absolutePathsByNodeId[nodeId] = node.resolvedPackage.id;
        return {};
    }
    const children = typeof node.children === 'function' ? node.children() : node.children;
    const parentPkgs = R.isEmpty(children)
        ? parentParentPkgs
        : Object.assign({}, parentParentPkgs, toPkgByName(R.keys(children).map((alias) => ({ alias, nodeId: children[alias], node: ctx.dependenciesTree[children[alias]] }))));
    const unknownResolvedPeersOfChildren = resolvePeersOfChildren(children, parentPkgs, ctx);
    const resolvedPeers = R.isEmpty(node.resolvedPackage.peerDependencies)
        ? {}
        : resolvePeers({
            dependenciesTree: ctx.dependenciesTree,
            node,
            nodeId,
            parentPkgs,
            prefix: ctx.prefix,
            strictPeerDependencies: ctx.strictPeerDependencies,
            usesExternalShrinkwrap: ctx.usesExternalShrinkwrap,
        });
    const allResolvedPeers = Object.assign(unknownResolvedPeersOfChildren, resolvedPeers);
    let modules;
    let absolutePath;
    const localLocation = path.join(ctx.virtualStoreDir, `.${pkgid_to_filename_1.default(node.resolvedPackage.id, ctx.prefix)}`);
    const isPure = R.isEmpty(allResolvedPeers);
    if (isPure) {
        modules = path.join(localLocation, 'node_modules');
        absolutePath = node.resolvedPackage.id;
        if (R.isEmpty(node.resolvedPackage.peerDependencies)) {
            ctx.purePkgs.add(node.resolvedPackage.id);
        }
    }
    else {
        const peersFolder = createPeersFolderName(R.keys(allResolvedPeers).map((alias) => ({
            name: alias,
            version: ctx.dependenciesTree[allResolvedPeers[alias]].resolvedPackage.version,
        })));
        modules = path.join(localLocation, peersFolder, 'node_modules');
        absolutePath = `${node.resolvedPackage.id}/${peersFolder}`;
    }
    ctx.absolutePathsByNodeId[nodeId] = absolutePath;
    if (!ctx.depGraph[absolutePath] || ctx.depGraph[absolutePath].depth > node.depth) {
        const independent = ctx.independentLeaves && R.isEmpty(node.children) && R.isEmpty(node.resolvedPackage.peerDependencies);
        const centralLocation = node.resolvedPackage.engineCache || path.join(node.resolvedPackage.path, 'node_modules', node.resolvedPackage.name);
        const peripheralLocation = !independent
            ? path.join(modules, node.resolvedPackage.name)
            : centralLocation;
        ctx.depGraph[absolutePath] = {
            absolutePath,
            additionalInfo: node.resolvedPackage.additionalInfo,
            centralLocation,
            children: Object.assign(children, resolvedPeers),
            depth: node.depth,
            dev: node.resolvedPackage.dev,
            fetchingFiles: node.resolvedPackage.fetchingFiles,
            fetchingRawManifest: node.resolvedPackage.fetchingRawManifest,
            hasBin: node.resolvedPackage.hasBin,
            hasBundledDependencies: node.resolvedPackage.hasBundledDependencies,
            id: node.resolvedPackage.id,
            independent,
            installable: node.installable,
            isBuilt: !!node.resolvedPackage.engineCache,
            isPure,
            modules,
            name: node.resolvedPackage.name,
            optional: node.resolvedPackage.optional,
            optionalDependencies: node.resolvedPackage.optionalDependencies,
            peripheralLocation,
            prepare: node.resolvedPackage.prepare,
            prod: node.resolvedPackage.prod,
            requiresBuild: node.resolvedPackage.requiresBuild,
            resolution: node.resolvedPackage.resolution,
            version: node.resolvedPackage.version,
        };
    }
    return allResolvedPeers;
}
function resolvePeersOfChildren(children, parentPkgs, ctx) {
    const allResolvedPeers = {};
    for (const childNodeId of R.values(children)) {
        Object.assign(allResolvedPeers, resolvePeersOfNode(childNodeId, parentPkgs, ctx));
    }
    const unknownResolvedPeersOfChildren = R.keys(allResolvedPeers)
        .filter((alias) => !children[alias])
        .reduce((acc, peer) => {
        acc[peer] = allResolvedPeers[peer];
        return acc;
    }, {});
    return unknownResolvedPeersOfChildren;
}
function resolvePeers(ctx) {
    const resolvedPeers = {};
    for (const peerName in ctx.node.resolvedPackage.peerDependencies) { // tslint:disable-line:forin
        const peerVersionRange = ctx.node.resolvedPackage.peerDependencies[peerName];
        let resolved = ctx.parentPkgs[peerName];
        if (!resolved || resolved.nodeId && !ctx.dependenciesTree[resolved.nodeId].installable) {
            try {
                const { version } = importFrom(ctx.prefix, `${peerName}/package.json`);
                resolved = {
                    depth: -1,
                    version,
                };
            }
            catch (err) {
                const friendlyPath = nodeIdToFriendlyPath(ctx.nodeId, ctx.dependenciesTree);
                const message = common_tags_1.oneLine `
          ${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.node.resolvedPackage)}
          requires a peer of ${peerName}@${peerVersionRange} but none was installed.`;
                if (ctx.strictPeerDependencies) {
                    throw new errorTypes_1.PnpmError('ERR_PNPM_MISSING_PEER_DEPENDENCY', message);
                }
                logger_1.default.warn({
                    message,
                    prefix: ctx.prefix,
                });
                continue;
            }
        }
        if (!semver.satisfies(resolved.version, peerVersionRange)) {
            const friendlyPath = nodeIdToFriendlyPath(ctx.nodeId, ctx.dependenciesTree);
            const message = common_tags_1.oneLine `
        ${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.node.resolvedPackage)}
        requires a peer of ${peerName}@${peerVersionRange} but version ${resolved.version} was installed.`;
            if (ctx.strictPeerDependencies) {
                throw new errorTypes_1.PnpmError('ERR_PNPM_INVALID_PEER_DEPENDENCY', message);
            }
            logger_1.default.warn({
                message,
                prefix: ctx.prefix,
            });
        }
        if (!ctx.usesExternalShrinkwrap && resolved.depth <= 0 || resolved.depth === ctx.node.depth + 1) {
            // if the resolved package is a top dependency
            // or the peer dependency is resolved from a regular dependency of the package
            // then there is no need to link it in
            continue;
        }
        if (resolved && resolved.nodeId)
            resolvedPeers[peerName] = resolved.nodeId;
    }
    return resolvedPeers;
}
function packageFriendlyId(pkg) {
    return `${pkg.name}@${pkg.version}`;
}
function nodeIdToFriendlyPath(nodeId, dependenciesTree) {
    const parts = utils_1.splitNodeId(nodeId).slice(2, -2);
    return R.tail(R.scan((prevNodeId, pkgId) => utils_1.createNodeId(prevNodeId, pkgId), utils_1.ROOT_NODE_ID, parts))
        .map((nid) => dependenciesTree[nid].resolvedPackage.name)
        .join(' > ');
}
function toPkgByName(nodes) {
    const pkgsByName = {};
    for (const node of nodes) {
        pkgsByName[node.alias] = {
            depth: node.node.depth,
            nodeId: node.nodeId,
            version: node.node.resolvedPackage.version,
        };
    }
    return pkgsByName;
}
function createPeersFolderName(peers) {
    const folderName = peers.map((peer) => `${peer.name.replace('/', '!')}@${peer.version}`).sort().join('+');
    // We don't want the folder name to get too long.
    // Otherwise, an ENAMETOOLONG error might happen.
    // see: https://github.com/pnpm/pnpm/issues/977
    //
    // A bigger limit might be fine but the md5 hash will be 32 symbols,
    // so for consistency's sake, we go with 32.
    if (folderName.length > 32) {
        return crypto.createHash('md5').update(folderName).digest('hex');
    }
    return folderName;
}
//# sourceMappingURL=resolvePeers.js.map