"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const R = require("ramda");
const semver = require("semver");
const pnpm_logger_1 = require("pnpm-logger");
const path = require("path");
function default_1(pkgsMap, topPkgIds, 
    // only the top dependencies that were already installed
    // to avoid warnings about unresolved peer dependencies
    topParents) {
    const tree = createTree(pkgsMap, topPkgIds);
    const pkgsByName = Object.assign(toPkgByName(R.props(tree.rootNodeIds, tree.nodes)), R.fromPairs(topParents.map((parent) => [
        parent.name,
        {
            version: parent.version,
            depth: 0
        }
    ])));
    const treeMaps = tree.rootNodeIds.map(rootNodeId => resolvePeersOfNode(rootNodeId, pkgsByName, tree.nodes));
    // because Object.assign will fail if called w/o arguments
    if (treeMaps.length === 0)
        return {};
    return Object.assign.apply(null, treeMaps);
}
exports.default = default_1;
function createTree(pkgsMap, pkgIds, depth, parentNodeId) {
    return R.props(pkgIds, pkgsMap)
        .reduce((acc, pkg) => {
        const node = createTreeNode(pkgsMap, pkg, depth || 0, parentNodeId);
        return {
            rootNodeIds: R.append(node.nodeId, acc.rootNodeIds),
            nodes: Object.assign(acc.nodes, node.childNodes)
        };
    }, { rootNodeIds: [], nodes: {} });
}
function createTreeNode(pkgsMap, pkg, depth, parentNodeId) {
    const nonCircularDeps = parentNodeId
        ? getNonCircularDependencies(parentNodeId, pkg.id, pkg.dependencies)
        : pkg.dependencies;
    const nodeId = parentNodeId
        ? relationCode(parentNodeId, pkg.id)
        : pkg.id;
    const tree = createTree(pkgsMap, nonCircularDeps, depth + 1, nodeId);
    return {
        nodeId,
        childNodes: Object.assign(tree.nodes, R.objOf(nodeId, {
            pkg,
            nodeId,
            children: tree.rootNodeIds,
            depth,
        }))
    };
}
function getNonCircularDependencies(parentNodeId, parentId, dependencyIds) {
    return dependencyIds.filter(depId => {
        const relation = relationCode(parentId, depId);
        return parentNodeId.indexOf(relation) === -1;
    });
}
function relationCode(parentId, dependencyId) {
    // using colon as it will never be used inside a package ID
    return `${parentId}:${dependencyId}`;
}
function resolvePeersOfNode(nodeId, parentPkgs, tree) {
    const node = tree[nodeId];
    const newParentPkgs = Object.assign({}, parentPkgs, { [node.pkg.name]: { version: node.pkg.version, nodeId: node.nodeId } }, toPkgByName(R.props(node.children, tree)));
    const resolvedPeers = resolvePeers(node.pkg.peerDependencies, node.pkg.id, newParentPkgs);
    const modules = path.join(node.pkg.localLocation, 'node_modules');
    const peerModules = !R.isEmpty(node.pkg.peerDependencies)
        ? path.join(node.pkg.localLocation, createPeersFolderName(R.props(resolvedPeers, tree).map(node => node.pkg)), 'node_modules')
        : undefined;
    const hardlinkedLocation = path.join(peerModules || modules, node.pkg.name);
    const trees = node.children.map(child => resolvePeersOfNode(child, newParentPkgs, tree));
    trees.push(R.objOf(nodeId, {
        name: node.pkg.name,
        hasBundledDependencies: node.pkg.hasBundledDependencies,
        fetchingFiles: node.pkg.fetchingFiles,
        path: node.pkg.path,
        peerModules,
        modules,
        hardlinkedLocation,
        resolvedPeers,
        children: node.children,
        depth: node.depth,
        id: node.pkg.id,
    }));
    return Object.assign.apply(null, trees);
}
function resolvePeers(peerDependencies, pkgId, parentPkgs) {
    return R.toPairs(peerDependencies)
        .map(R.apply((peerName, peerVersionRange) => {
        const resolved = parentPkgs[peerName];
        if (!resolved) {
            pnpm_logger_1.default.warn(`${pkgId} requires a peer of ${peerName}@${peerVersionRange} but none was installed.`);
            return null;
        }
        if (!semver.satisfies(resolved.version, peerVersionRange)) {
            pnpm_logger_1.default.warn(`${pkgId} requires a peer of ${peerName}@${peerVersionRange} but version ${resolved.version} was installed.`);
        }
        if (resolved.depth === 0) {
            // if the resolved package is a top dependency then there is no need to link it in
            return null;
        }
        return resolved && resolved.nodeId;
    }))
        .filter(Boolean);
}
function toPkgByName(pkgs) {
    const toNameAndPkg = R.map((node) => [
        node.pkg.name,
        {
            version: node.pkg.version,
            nodeId: node.nodeId,
            depth: node.depth,
        }
    ]);
    return R.fromPairs(toNameAndPkg(pkgs));
}
function createPeersFolderName(peers) {
    return peers.map(peer => `${peer.name.replace('/', '!')}@${peer.version}`).sort().join('+');
}
//# sourceMappingURL=resolvePeers.js.map