'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _regenerator;

function _load_regenerator() {
  return _regenerator = _interopRequireDefault(require('babel-runtime/regenerator'));
}

var _bluebird;

function _load_bluebird() {
  return _bluebird = require('bluebird');
}

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _path;

function _load_path() {
  return _path = _interopRequireDefault(require('path'));
}

var _ramda;

function _load_ramda() {
  return _ramda = _interopRequireDefault(require('ramda'));
}

var _glob;

function _load_glob() {
  return _glob = _interopRequireDefault(require('glob'));
}

var _bitId;

function _load_bitId() {
  return _bitId = require('../bit-id');
}

var _constants;

function _load_constants() {
  return _constants = require('../constants');
}

var _logger;

function _load_logger() {
  return _logger = _interopRequireDefault(require('../logger/logger'));
}

var _utils;

function _load_utils() {
  return _utils = require('../utils');
}

var _linkGenerator;

function _load_linkGenerator() {
  return _linkGenerator = require('./link-generator');
}

var _linkContent;

function _load_linkContent() {
  return _linkContent = require('./link-content');
}

var _componentNodeModulesPath;

function _load_componentNodeModulesPath() {
  return _componentNodeModulesPath = _interopRequireDefault(require('../utils/bit/component-node-modules-path'));
}

var _bitMap;

function _load_bitMap() {
  return _bitMap = _interopRequireDefault(require('../consumer/bit-map/bit-map'));
}

var _symlink;

function _load_symlink() {
  return _symlink = _interopRequireDefault(require('./symlink'));
}

var _dataToPersist;

function _load_dataToPersist() {
  return _dataToPersist = _interopRequireDefault(require('../consumer/component/sources/data-to-persist'));
}

var _linkFile;

function _load_linkFile() {
  return _linkFile = _interopRequireDefault(require('./link-file'));
}

var _componentsList;

function _load_componentsList() {
  return _componentsList = _interopRequireDefault(require('../consumer/component/components-list'));
}

var _packageJsonUtils;

function _load_packageJsonUtils() {
  return _packageJsonUtils = require('../consumer/component/package-json-utils');
}

var _packageJsonFile;

function _load_packageJsonFile() {
  return _packageJsonFile = _interopRequireDefault(require('../consumer/component/package-json-file'));
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/**
 * link given components to node_modules, so it's possible to use absolute link instead of relative
 * for example, require('@bit/remote-scope.bar.foo)
 */
var NodeModuleLinker = function () {
  function NodeModuleLinker(components, consumer, bitMap) {
    _classCallCheck(this, NodeModuleLinker);

    this.components = (_componentsList || _load_componentsList()).default.getUniqueComponents(components);
    this.consumer = consumer;
    this.bitMap = bitMap;
    this.dataToPersist = new (_dataToPersist || _load_dataToPersist()).default();
  } // preparation for the capsule, which is going to have only BitMap with no Consumer


  _createClass(NodeModuleLinker, [{
    key: 'link',
    value: function () {
      var _ref = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee() {
        var links, linksResults;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                _context.next = 2;
                return this.getLinks();

              case 2:
                links = _context.sent;
                linksResults = this.getLinksResults();

                if (this.consumer) links.addBasePath(this.consumer.getPath());
                _context.next = 7;
                return links.persistAllToFS();

              case 7:
                return _context.abrupt('return', linksResults);

              case 8:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

      function link() {
        return _ref.apply(this, arguments);
      }

      return link;
    }()
  }, {
    key: 'getLinks',
    value: function () {
      var _ref2 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2() {
        var _this = this;

        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                this.dataToPersist = new (_dataToPersist || _load_dataToPersist()).default();
                _context2.next = 3;
                return this._populateShouldDependenciesSavedAsComponentsData();

              case 3:
                _context2.next = 5;
                return Promise.all(this.components.map(function (component) {
                  var componentId = component.id.toString();
                  (_logger || _load_logger()).default.debug(`linking component to node_modules: ${componentId}`);
                  var componentMap = _this.bitMap.getComponent(component.id);
                  component.componentMap = componentMap;
                  switch (componentMap.origin) {
                    case (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED:
                      return _this._populateImportedComponentsLinks(component);
                    case (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED:
                      return _this._populateNestedComponentsLinks(component);
                    case (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED:
                      return _this._populateAuthoredComponentsLinks(component);
                    default:
                      throw new Error(`ComponentMap.origin ${componentMap.origin} of ${componentId} is not recognized`);
                  }
                }));

              case 5:
                return _context2.abrupt('return', this.dataToPersist);

              case 6:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this);
      }));

      function getLinks() {
        return _ref2.apply(this, arguments);
      }

      return getLinks;
    }()
  }, {
    key: 'getLinksResults',
    value: function getLinksResults() {
      var linksResults = [];
      var getExistingLinkResult = function getExistingLinkResult(id) {
        return linksResults.find(function (linkResult) {
          return linkResult.id.isEqual(id);
        });
      };
      var addLinkResult = function addLinkResult(id, from, to) {
        if (!id) return;
        var existingLinkResult = getExistingLinkResult(id);
        if (existingLinkResult) {
          existingLinkResult.bound.push({ from, to });
        } else {
          linksResults.push({ id, bound: [{ from, to }] });
        }
      };
      this.dataToPersist.symlinks.forEach(function (symlink) {
        addLinkResult(symlink.componentId, symlink.src, symlink.dest);
      });
      this.dataToPersist.files.forEach(function (file) {
        addLinkResult(file.componentId, file.srcPath, file.path);
      });
      this.components.forEach(function (component) {
        var existingLinkResult = getExistingLinkResult(component.id);
        if (!existingLinkResult) {
          linksResults.push({ id: component.id, bound: [] });
        }
      });
      return linksResults;
    }
  }, {
    key: '_populateImportedComponentsLinks',
    value: function () {
      var _ref3 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3(component) {
        var componentMap, componentId, bindingPrefix, linkPath, srcTarget, shouldDistsBeInsideTheComponent, distTarget, packagesSymlinks, distSymlink, dependenciesLinks, missingDependenciesLinks, missingCustomResolvedLinks;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                componentMap = component.componentMap;
                componentId = component.id;

                if (componentId.hasScope()) {
                  _context3.next = 4;
                  break;
                }

                return _context3.abrupt('return');

              case 4:
                // when isolating new components// from isolated;
                bindingPrefix = this.consumer ? this.consumer.config.bindingPrefix : (_constants || _load_constants()).DEFAULT_BINDINGS_PREFIX;
                linkPath = (0, (_componentNodeModulesPath || _load_componentNodeModulesPath()).default)(bindingPrefix, componentId);
                // when a user moves the component directory, use component.writtenPath to find the correct target
                // $FlowFixMe

                srcTarget = component.writtenPath || componentMap.rootDir;
                shouldDistsBeInsideTheComponent = this.consumer ? this.consumer.shouldDistsBeInsideTheComponent() : true;

                if (this.consumer && !component.dists.isEmpty() && component.dists.writeDistsFiles && !shouldDistsBeInsideTheComponent) {
                  distTarget = component.dists.getDistDir(this.consumer, componentMap.getRootDir());
                  packagesSymlinks = this._getSymlinkPackages(srcTarget, distTarget, component);

                  this.dataToPersist.addManySymlinks(packagesSymlinks);
                  distSymlink = (_symlink || _load_symlink()).default.makeInstance(distTarget, linkPath, componentId);

                  distSymlink.forDistOutsideComponentsDir = true;
                  this.dataToPersist.addSymlink(distSymlink);
                } else {
                  this.dataToPersist.addSymlink((_symlink || _load_symlink()).default.makeInstance(srcTarget, linkPath, componentId));
                }

                if (component.hasDependencies()) {
                  dependenciesLinks = this._getDependenciesLinks(component);

                  this.dataToPersist.addManySymlinks(dependenciesLinks);
                }
                missingDependenciesLinks = this.consumer && component.issues && component.issues.missingLinks ? this._getMissingLinks(component) : [];

                this.dataToPersist.addManySymlinks(missingDependenciesLinks);

                if (!(this.consumer && component.issues && component.issues.missingCustomModuleResolutionLinks)) {
                  _context3.next = 18;
                  break;
                }

                _context3.next = 15;
                return this._getMissingCustomResolvedLinks(component);

              case 15:
                missingCustomResolvedLinks = _context3.sent;

                this.dataToPersist.addManyFiles(missingCustomResolvedLinks.files);
                this.dataToPersist.addManySymlinks(missingCustomResolvedLinks.symlinks);

              case 18:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function _populateImportedComponentsLinks(_x) {
        return _ref3.apply(this, arguments);
      }

      return _populateImportedComponentsLinks;
    }()
    /**
     * nested components are linked only during the import process. running `bit link` command won't
     * link them because the nested dependencies are not loaded during consumer.loadComponents()
     */

  }, {
    key: '_populateNestedComponentsLinks',
    value: function _populateNestedComponentsLinks(component) {
      if (component.hasDependencies()) {
        var dependenciesLinks = this._getDependenciesLinks(component);
        this.dataToPersist.addManySymlinks(dependenciesLinks);
      }
    }
    /**
     * authored components are linked only when they were exported before
     */

  }, {
    key: '_populateAuthoredComponentsLinks',
    value: function _populateAuthoredComponentsLinks(component) {
      var _this2 = this;

      var componentId = component.id;
      if (!componentId.scope) return; // scope is a must to generate the link
      var filesToBind = component.componentMap.getFilesRelativeToConsumer();
      component.dists.updateDistsPerWorkspaceConfig(component.id, this.consumer, component.componentMap);
      filesToBind.forEach(function (file) {
        var isMain = file === component.componentMap.mainFile;
        var possiblyDist = component.dists.calculateDistFileForAuthored((_path || _load_path()).default.normalize(file), _this2.consumer, isMain);
        var dest = (_path || _load_path()).default.join((0, (_componentNodeModulesPath || _load_componentNodeModulesPath()).default)(component.bindingPrefix, componentId), file);
        var destRelative = _this2._getPathRelativeRegardlessCWD((_path || _load_path()).default.dirname(dest), possiblyDist);
        var fileContent = (0, (_linkContent || _load_linkContent()).getLinkToFileContent)(destRelative);
        if (fileContent) {
          var linkFile = (_linkFile || _load_linkFile()).default.load({
            filePath: dest,
            content: fileContent,
            srcPath: file,
            componentId,
            override: true
          });
          _this2.dataToPersist.addFile(linkFile);
        } else {
          // it's an un-supported file, create a symlink instead
          _this2.dataToPersist.addSymlink((_symlink || _load_symlink()).default.makeInstance(file, dest, componentId));
        }
      });
      this._createPackageJsonForAuthor(component);
    }
    /**
     * When the dists is outside the components directory, it doesn't have access to the node_modules of the component's
     * root-dir. The solution is to go through the node_modules packages one by one and symlink them.
     */

  }, {
    key: '_getSymlinkPackages',
    value: function _getSymlinkPackages(from, to, component) {
      if (!this.consumer) throw new Error('getSymlinkPackages expects the Consumer to be defined');
      var dependenciesSavedAsComponents = component.dependenciesSavedAsComponents;
      var fromNodeModules = (_path || _load_path()).default.join(from, 'node_modules');
      var toNodeModules = (_path || _load_path()).default.join(to, 'node_modules');
      (_logger || _load_logger()).default.debug(`symlinkPackages for dists outside the component directory from ${fromNodeModules} to ${toNodeModules}`);
      var unfilteredDirs = (_glob || _load_glob()).default.sync('*', { cwd: fromNodeModules });
      // when dependenciesSavedAsComponents the node_modules/@bit has real link files, we don't want to touch them
      // otherwise, node_modules/@bit has packages as any other directory in node_modules
      var dirsToFilter = dependenciesSavedAsComponents ? [this.consumer.config.bindingPrefix] : [];
      var customResolvedData = component.dependencies.getCustomResolvedData();
      if (!(_ramda || _load_ramda()).default.isEmpty(customResolvedData)) {
        // filter out packages that are actually symlinks to dependencies
        Object.keys(customResolvedData).forEach(function (importSource) {
          return dirsToFilter.push((0, (_utils || _load_utils()).first)(importSource.split('/')));
        });
      }
      var dirs = dirsToFilter.length ? unfilteredDirs.filter(function (dir) {
        return !dirsToFilter.includes(dir);
      }) : unfilteredDirs;
      if (!dirs.length) return [];
      return dirs.map(function (dir) {
        var fromDir = (_path || _load_path()).default.join(fromNodeModules, dir);
        var toDir = (_path || _load_path()).default.join(toNodeModules, dir);
        return (_symlink || _load_symlink()).default.makeInstance(fromDir, toDir);
      });
    }
  }, {
    key: '_getDependenciesLinks',
    value: function _getDependenciesLinks(component) {
      var _this3 = this;

      // $FlowFixMe
      var componentMap = component.componentMap;
      var getSymlinks = function getSymlinks(dependency) {
        var dependencyComponentMap = _this3.bitMap.getComponentIfExist(dependency.id);
        var dependenciesLinks = [];
        if (!dependencyComponentMap || !dependencyComponentMap.rootDir) return dependenciesLinks;
        if (!dependency.id.hasScope()) return dependenciesLinks; // when isolating new components
        var parentRootDir = componentMap.getRootDir();
        var dependencyRootDir = dependencyComponentMap.getRootDir();
        dependenciesLinks.push(_this3._getDependencyLink(parentRootDir, dependency.id, dependencyRootDir, component.bindingPrefix));
        if (_this3.consumer && !_this3.consumer.shouldDistsBeInsideTheComponent()) {
          // when dists are written outside the component, it doesn't matter whether a component
          // has dists files or not, in case it doesn't have, the files are copied from the component
          // dir into the dist dir. (see consumer-component.write())
          var _from = component.dists.getDistDirForConsumer(_this3.consumer, parentRootDir);
          var _to = component.dists.getDistDirForConsumer(_this3.consumer, dependencyRootDir);
          var distSymlink = _this3._getDependencyLink(_from, dependency.id, _to, component.bindingPrefix);
          distSymlink.forDistOutsideComponentsDir = true;
          dependenciesLinks.push(distSymlink);
        }
        return dependenciesLinks;
      };
      var symlinks = component.getAllDependencies().map(function (dependency) {
        return getSymlinks(dependency);
      });
      return (_ramda || _load_ramda()).default.flatten(symlinks);
    }
  }, {
    key: '_getMissingLinks',
    value: function _getMissingLinks(component) {
      var _this4 = this;

      var missingLinks = component.issues.missingLinks;
      var result = Object.keys(component.issues.missingLinks).map(function (key) {
        return missingLinks[key].map(function (dependencyIdRaw) {
          var dependencyId = _this4.bitMap.getBitId(dependencyIdRaw, { ignoreVersion: true });
          var dependencyComponentMap = _this4.bitMap.getComponent(dependencyId);
          if (!dependencyComponentMap.rootDir) return null;
          return _this4._getDependencyLink(component.componentMap.rootDir, dependencyId, dependencyComponentMap.rootDir, component.bindingPrefix);
        }).filter(function (x) {
          return x;
        });
      });
      return (_ramda || _load_ramda()).default.flatten(result);
    }
  }, {
    key: '_getDependencyLink',
    value: function _getDependencyLink(parentRootDir, bitId, rootDir, bindingPrefix) {
      var relativeDestPath = (0, (_componentNodeModulesPath || _load_componentNodeModulesPath()).default)(bindingPrefix, bitId);
      var destPathInsideParent = (_path || _load_path()).default.join(parentRootDir, relativeDestPath);
      return (_symlink || _load_symlink()).default.makeInstance(rootDir, destPathInsideParent, bitId);
    }
  }, {
    key: '_getMissingCustomResolvedLinks',
    value: function () {
      var _ref4 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4(component) {
        var componentWithDependencies, missingLinks, dependenciesStr, componentsDependenciesLinks;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                if (component.componentFromModel) {
                  _context4.next = 2;
                  break;
                }

                return _context4.abrupt('return', new (_dataToPersist || _load_dataToPersist()).default());

              case 2:
                if (this.consumer) {
                  _context4.next = 4;
                  break;
                }

                throw new Error('_getMissingCustomResolvedLinks expects to have consumer set');

              case 4:
                _context4.next = 6;
                return component.toComponentWithDependencies(this.consumer);

              case 6:
                componentWithDependencies = _context4.sent;
                missingLinks = component.issues.missingCustomModuleResolutionLinks;
                dependenciesStr = (_ramda || _load_ramda()).default.flatten(Object.keys(missingLinks).map(function (fileName) {
                  return missingLinks[fileName];
                }));

                component.copyDependenciesFromModel(dependenciesStr);
                componentsDependenciesLinks = (0, (_linkGenerator || _load_linkGenerator()).getComponentsDependenciesLinks)([componentWithDependencies], this.consumer, false, this.bitMap);
                return _context4.abrupt('return', componentsDependenciesLinks);

              case 12:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this);
      }));

      function _getMissingCustomResolvedLinks(_x2) {
        return _ref4.apply(this, arguments);
      }

      return _getMissingCustomResolvedLinks;
    }()

    /**
     * path.resolve uses current working dir.
     * for us, the cwd is not important. a user may running bit command from an inner dir.
     */

  }, {
    key: '_getPathRelativeRegardlessCWD',
    value: function _getPathRelativeRegardlessCWD(from, to) {
      var fromLinux = (0, (_utils || _load_utils()).pathNormalizeToLinux)(from);
      var toLinux = (0, (_utils || _load_utils()).pathNormalizeToLinux)(to);
      // change them to absolute so path.relative won't consider the cwd
      return (0, (_utils || _load_utils()).pathRelativeLinux)(`/${fromLinux}`, `/${toLinux}`);
    }

    /**
     * create package.json on node_modules/@bit/component-name/package.json with a property 'main'
     * pointing to the component's main file.
     * It is needed for Authored components only.
     * Since an authored component doesn't have rootDir, it's impossible to symlink to the component directory.
     * It makes it easier for Author to use absolute syntax between their own components.
     */

  }, {
    key: '_createPackageJsonForAuthor',
    value: function _createPackageJsonForAuthor(component) {
      var hasPackageJsonAsComponentFile = component.files.some(function (file) {
        return file.relative === (_constants || _load_constants()).PACKAGE_JSON;
      });
      if (hasPackageJsonAsComponentFile) return; // don't generate package.json on top of the user package.json
      var dest = (_path || _load_path()).default.join((0, (_componentNodeModulesPath || _load_componentNodeModulesPath()).default)(component.bindingPrefix, component.id));
      var packageJson = (_packageJsonFile || _load_packageJsonFile()).default.createFromComponent(dest, component);
      this.dataToPersist.addFile(packageJson.toJSONFile());
    }

    /**
     * links are normally generated by `bit import`, `bit link` and `bit install`.
     * for `bit import` the data about whether dependenciesSavedAsComponents is already populated
     * for the rest, it's not.
     * @todo: avoid repopulating for imported. (not easy because by default, all components get "true").
     */

  }, {
    key: '_populateShouldDependenciesSavedAsComponentsData',
    value: function () {
      var _ref5 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5() {
        var bitIds, shouldDependenciesSavedAsComponents;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                if (!(!this.components.length || !this.consumer)) {
                  _context5.next = 2;
                  break;
                }

                return _context5.abrupt('return');

              case 2:
                bitIds = this.components.map(function (c) {
                  return c.id;
                });
                _context5.next = 5;
                return this.consumer.shouldDependenciesSavedAsComponents(bitIds);

              case 5:
                shouldDependenciesSavedAsComponents = _context5.sent;

                this.components.forEach(function (component) {
                  var shouldSavedAsComponents = shouldDependenciesSavedAsComponents.find(function (c) {
                    return c.id.isEqual(component.id);
                  });
                  if (!shouldSavedAsComponents) {
                    throw new Error(`_populateShouldDependenciesSavedAsComponentsData, saveDependenciesAsComponents is missing for ${component.id.toString()}`);
                  }
                  component.dependenciesSavedAsComponents = shouldSavedAsComponents.saveDependenciesAsComponents;
                });

              case 7:
              case 'end':
                return _context5.stop();
            }
          }
        }, _callee5, this);
      }));

      function _populateShouldDependenciesSavedAsComponentsData() {
        return _ref5.apply(this, arguments);
      }

      return _populateShouldDependenciesSavedAsComponentsData;
    }()
  }]);

  return NodeModuleLinker;
}();

exports.default = NodeModuleLinker;