"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const package_store_1 = require("package-store");
const R = require("ramda");
const semver = require("semver");
const pnpm_logger_1 = require("pnpm-logger");
const path = require("path");
function default_1(tree, rootNodeIds, topPkgIds, 
    // only the top dependencies that were already installed
    // to avoid warnings about unresolved peer dependencies
    topParents, independentLeaves, nodeModules) {
    const pkgsByName = R.fromPairs(topParents.map((parent) => [
        parent.name,
        {
            version: parent.version,
            depth: 0
        }
    ]));
    const nodeIdToResolvedId = {};
    const resolvedTree = {};
    resolvePeersOfChildren(new Set(rootNodeIds), pkgsByName, {
        tree,
        nodeIdToResolvedId,
        resolvedTree,
        independentLeaves,
        nodeModules,
        purePkgs: new Set(),
    });
    R.values(resolvedTree).forEach(node => {
        node.children = node.children.map(child => nodeIdToResolvedId[child]);
    });
    return resolvedTree;
}
exports.default = default_1;
function resolvePeersOfNode(nodeId, parentPkgs, ctx) {
    const node = ctx.tree[nodeId];
    if (ctx.purePkgs.has(node.pkg.id) && ctx.resolvedTree[node.pkg.id].depth <= node.depth) {
        ctx.nodeIdToResolvedId[nodeId] = node.pkg.id;
        return new Set();
    }
    const childrenSet = new Set(node.children);
    const unknownResolvedPeersOfChildren = resolvePeersOfChildren(childrenSet, parentPkgs, ctx);
    const resolvedPeers = R.isEmpty(node.pkg.peerDependencies)
        ? new Set()
        : resolvePeers(node, Object.assign({}, parentPkgs, toPkgByName(R.props(node.children, ctx.tree))), ctx.tree);
    unknownResolvedPeersOfChildren.delete(nodeId);
    const allResolvedPeers = union(unknownResolvedPeersOfChildren, resolvedPeers);
    let modules;
    let absolutePath;
    const localLocation = path.join(ctx.nodeModules, `.${package_store_1.pkgIdToFilename(node.pkg.id)}`);
    if (R.isEmpty(node.pkg.peerDependencies)) {
        ctx.purePkgs.add(node.pkg.id);
    }
    if (!allResolvedPeers.size) {
        modules = path.join(localLocation, 'node_modules');
        absolutePath = node.pkg.id;
    }
    else {
        const peersFolder = createPeersFolderName(R.props(Array.from(allResolvedPeers), ctx.tree).map(node => node.pkg));
        modules = path.join(localLocation, peersFolder, 'node_modules');
        absolutePath = `${node.pkg.id}/${peersFolder}`;
    }
    ctx.nodeIdToResolvedId[nodeId] = absolutePath;
    if (!ctx.resolvedTree[absolutePath] || ctx.resolvedTree[absolutePath].depth > node.depth) {
        const independent = ctx.independentLeaves && !node.children.length && R.isEmpty(node.pkg.peerDependencies);
        const pathToUnpacked = path.join(node.pkg.path, 'node_modules', node.pkg.name);
        const hardlinkedLocation = !independent
            ? path.join(modules, node.pkg.name)
            : pathToUnpacked;
        ctx.resolvedTree[absolutePath] = {
            name: node.pkg.name,
            version: node.pkg.version,
            hasBundledDependencies: node.pkg.hasBundledDependencies,
            fetchingFiles: node.pkg.fetchingFiles,
            resolution: node.pkg.resolution,
            path: pathToUnpacked,
            modules,
            hardlinkedLocation,
            independent,
            optionalDependencies: node.pkg.optionalDependencies,
            children: Array.from(union(childrenSet, resolvedPeers)),
            depth: node.depth,
            absolutePath,
            prod: node.pkg.prod,
            dev: node.pkg.dev,
            optional: node.pkg.optional,
            id: node.pkg.id,
            installable: node.installable,
            pkg: node.pkg.pkg,
        };
    }
    return allResolvedPeers;
}
function addMany(a, b) {
    for (const el of Array.from(b)) {
        a.add(el);
    }
    return a;
}
function union(a, b) {
    return new Set(Array.from(a).concat(Array.from(b)));
}
function difference(a, b) {
    return new Set(Array.from(a).filter(el => !b.has(el)));
}
function resolvePeersOfChildren(children, parentParentPkgs, ctx) {
    const childrenArray = Array.from(children);
    let allResolvedPeers = new Set();
    const parentPkgs = Object.assign({}, parentParentPkgs, toPkgByName(R.props(childrenArray, ctx.tree)));
    for (const child of childrenArray) {
        addMany(allResolvedPeers, resolvePeersOfNode(child, parentPkgs, ctx));
    }
    const unknownResolvedPeersOfChildren = difference(allResolvedPeers, children);
    return unknownResolvedPeersOfChildren;
}
function resolvePeers(node, parentPkgs, tree) {
    const resolvedPeers = new Set();
    for (const peerName in node.pkg.peerDependencies) {
        const peerVersionRange = node.pkg.peerDependencies[peerName];
        const resolved = parentPkgs[peerName];
        if (!resolved || resolved.nodeId && !tree[resolved.nodeId].installable) {
            pnpm_logger_1.default.warn(`${node.pkg.id} requires a peer of ${peerName}@${peerVersionRange} but none was installed.`);
            continue;
        }
        if (!semver.satisfies(resolved.version, peerVersionRange)) {
            pnpm_logger_1.default.warn(`${node.pkg.id} requires a peer of ${peerName}@${peerVersionRange} but version ${resolved.version} was installed.`);
        }
        if (resolved.depth === 0 || resolved.depth === 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.add(resolved.nodeId);
    }
    return resolvedPeers;
}
function toPkgByName(nodes) {
    const pkgsByName = {};
    for (const node of nodes) {
        pkgsByName[node.pkg.name] = {
            version: node.pkg.version,
            nodeId: node.nodeId,
            depth: node.depth,
        };
    }
    return pkgsByName;
}
function createPeersFolderName(peers) {
    return peers.map(peer => `${peer.name.replace('/', '!')}@${peer.version}`).sort().join('+');
}
//# sourceMappingURL=resolvePeers.js.map