'use strict';

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

var _regenerator;

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

var writeMissingCustomResolvedLinks = function () {
  var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(consumer, component) {
    var componentWithDependencies, missingLinks, dependenciesStr;
    return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            if (component.componentFromModel) {
              _context.next = 2;
              break;
            }

            return _context.abrupt('return', []);

          case 2:
            _context.next = 4;
            return component.toComponentWithDependencies(consumer);

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

            component.copyDependenciesFromModel(dependenciesStr);
            _context.next = 10;
            return (0, (_linkGenerator || _load_linkGenerator()).writeDependencyLinks)([componentWithDependencies], consumer, false);

          case 10:
            return _context.abrupt('return', []);

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

  return function writeMissingCustomResolvedLinks(_x, _x2) {
    return _ref.apply(this, arguments);
  };
}();

var _linkImportedComponents = function () {
  var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(consumer, component, componentMap) {
    var componentId, relativeLinkPath, linkPath, srcTarget, distTarget, bound, boundDependencies, boundMissingDependencies, boundMissingCustomResolvedLinks, boundAll;
    return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
      while (1) {
        switch (_context2.prev = _context2.next) {
          case 0:
            componentId = component.id;
            relativeLinkPath = (_consumer || _load_consumer()).default.getNodeModulesPathOfComponent(consumer.bitJson.bindingPrefix, componentId);
            linkPath = consumer.toAbsolutePath(relativeLinkPath);
            // when a user moves the component directory, use component.writtenPath to find the correct target

            srcTarget = component.writtenPath || consumer.toAbsolutePath(componentMap.rootDir);

            if (!component.dists.isEmpty() && component.dists.writeDistsFiles && !consumer.shouldDistsBeInsideTheComponent()) {
              distTarget = component.dists.getDistDirForConsumer(consumer, componentMap.rootDir);

              symlinkPackages(srcTarget, distTarget, consumer, component);
              createSymlinkOrCopy(componentId, distTarget, linkPath);
            } else {
              createSymlinkOrCopy(componentId, srcTarget, linkPath);
            }

            bound = [{ from: componentMap.rootDir, to: relativeLinkPath }];
            boundDependencies = component.hasDependencies() ? writeDependenciesLinks(component, componentMap, consumer) : [];
            boundMissingDependencies = component.missingDependencies && component.missingDependencies.missingLinks ? writeMissingLinks(consumer, component, componentMap) : [];

            if (!(component.missingDependencies && component.missingDependencies.missingCustomModuleResolutionLinks)) {
              _context2.next = 14;
              break;
            }

            _context2.next = 11;
            return writeMissingCustomResolvedLinks(consumer, component);

          case 11:
            _context2.t0 = _context2.sent;
            _context2.next = 15;
            break;

          case 14:
            _context2.t0 = [];

          case 15:
            boundMissingCustomResolvedLinks = _context2.t0;
            boundAll = bound.concat([].concat(_toConsumableArray((_ramda || _load_ramda()).default.flatten(boundDependencies)), _toConsumableArray(boundMissingDependencies), _toConsumableArray(boundMissingCustomResolvedLinks)));
            // $FlowFixMe

            return _context2.abrupt('return', { id: componentId, bound: boundAll });

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

  return function _linkImportedComponents(_x3, _x4, _x5) {
    return _ref2.apply(this, arguments);
  };
}();

/**
 * 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()
 */


var _path;

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

var _fsExtra;

function _load_fsExtra() {
  return _fsExtra = _interopRequireDefault(require('fs-extra'));
}

var _ramda;

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

var _symlinkOrCopy;

function _load_symlinkOrCopy() {
  return _symlinkOrCopy = _interopRequireDefault(require('symlink-or-copy'));
}

var _glob;

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

var _bitId;

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

var _component;

function _load_component() {
  return _component = _interopRequireDefault(require('../consumer/component'));
}

var _constants;

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

var _componentMap;

function _load_componentMap() {
  return _componentMap = _interopRequireDefault(require('../consumer/bit-map/component-map'));
}

var _logger;

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

var _utils;

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

var _consumer;

function _load_consumer() {
  return _consumer = _interopRequireDefault(require('../consumer/consumer'));
}

var _linkGenerator;

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

var _generalError;

function _load_generalError() {
  return _generalError = _interopRequireDefault(require('../error/general-error'));
}

var _dependencies;

function _load_dependencies() {
  return _dependencies = require('../consumer/component/dependencies');
}

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

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

/**
 * @param componentId
 * @param srcPath the path where the symlink is pointing to
 * @param destPath the path where to write the symlink
 */
function createSymlinkOrCopy(componentId, srcPath, destPath) {
  (_fsExtra || _load_fsExtra()).default.removeSync(destPath); // in case a component has been moved
  (_fsExtra || _load_fsExtra()).default.ensureDirSync((_path || _load_path()).default.dirname(destPath));
  try {
    (_logger || _load_logger()).default.debug(`generating a symlink on ${destPath} pointing to ${srcPath}`);
    (_symlinkOrCopy || _load_symlinkOrCopy()).default.sync(srcPath, destPath);
  } catch (err) {
    throw new (_generalError || _load_generalError()).default(`failed to link a component ${componentId.toString()}.
         Symlink (or maybe copy for Windows) from: ${srcPath}, to: ${destPath} was failed.
         Original error: ${err}`);
  }
}

function writeDependencyLink(parentRootDir, // absolute path
bitId, rootDir, // absolute path
bindingPrefix) {
  var relativeDestPath = (_consumer || _load_consumer()).default.getNodeModulesPathOfComponent(bindingPrefix, bitId);
  var destPath = (_path || _load_path()).default.join(parentRootDir, relativeDestPath);
  createSymlinkOrCopy(bitId, rootDir, destPath);

  return { from: parentRootDir, to: rootDir };
}

/**
 * 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.
 */
function symlinkPackages(from, to, consumer, component) {
  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 ? [consumer.bitJson.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;
  (_fsExtra || _load_fsExtra()).default.ensureDirSync(toNodeModules);
  dirs.forEach(function (dir) {
    var fromDir = (_path || _load_path()).default.join(fromNodeModules, dir);
    var toDir = (_path || _load_path()).default.join(toNodeModules, dir);
    (_logger || _load_logger()).default.debug(`removing current ${toDir} and generating a symlink for package, from ${fromDir} to ${toDir}`);
    (_fsExtra || _load_fsExtra()).default.removeSync(toDir);
    (_symlinkOrCopy || _load_symlinkOrCopy()).default.sync(fromDir, toDir);
  });
}

function writeDependenciesLinks(component, componentMap, consumer) {
  return component.getAllDependencies().map(function (dependency) {
    var dependencyComponentMap = consumer.bitMap.getComponent(dependency.id);
    var writtenLinks = [];
    if (!dependencyComponentMap) return writtenLinks;
    var parentRootDir = componentMap.rootDir || '.'; // compilers/testers don't have rootDir
    writtenLinks.push(writeDependencyLink(consumer.toAbsolutePath(parentRootDir), dependency.id, consumer.toAbsolutePath(dependencyComponentMap.rootDir), component.bindingPrefix));
    if (!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(consumer, componentMap.rootDir);
      var _to = component.dists.getDistDirForConsumer(consumer, dependencyComponentMap.rootDir);
      writtenLinks.push(writeDependencyLink(_from, dependency.id, _to, component.bindingPrefix));
      // @todo: why is it from a component to its dependency? shouldn't it be from component src to dist/component?
      symlinkPackages(_from, _to, consumer, component);
    }
    return writtenLinks;
  });
}

/**
 * Link from node_modules/@bit/component-name/index.js 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.
 */
function linkToMainFile(component, componentMap, componentId, consumer) {
  component.dists.updateDistsPerConsumerBitJson(component.id, consumer, componentMap);
  var mainFile = component.dists.calculateMainDistFileForAuthored(component.mainFile, consumer);
  var indexFileName = (0, (_linkGenerator || _load_linkGenerator()).getIndexFileName)(mainFile);
  var dest = (_path || _load_path()).default.join((_consumer || _load_consumer()).default.getNodeModulesPathOfComponent(component.bindingPrefix, componentId), indexFileName);
  var destRelative = (0, (_utils || _load_utils()).pathRelativeLinux)((_path || _load_path()).default.dirname(dest), mainFile);
  var fileContent = (0, (_linkGenerator || _load_linkGenerator()).getLinkContent)(destRelative);
  (_fsExtra || _load_fsExtra()).default.outputFileSync(dest, fileContent);
}

function writeMissingLinks(consumer, component, componentMap) {
  var missingLinks = component.missingDependencies.missingLinks;
  var result = Object.keys(component.missingDependencies.missingLinks).map(function (key) {
    return missingLinks[key].map(function (dependencyIdStr) {
      var dependencyId = consumer.bitMap.getExistingComponentId(dependencyIdStr);
      if (!dependencyId) return null;

      var dependencyComponentMap = consumer.bitMap.getComponent(dependencyId);
      return writeDependencyLink(consumer.toAbsolutePath(componentMap.rootDir), (_bitId || _load_bitId()).BitId.parse(dependencyId), consumer.toAbsolutePath(dependencyComponentMap.rootDir), component.bindingPrefix);
    });
  });
  return (_ramda || _load_ramda()).default.flatten(result);
}

function _linkNestedComponents(consumer, component, componentMap) {
  var componentId = component.id;
  if (!component.hasDependencies()) return { id: componentId, bound: [] };
  var bound = writeDependenciesLinks(component, componentMap, consumer);
  return { id: componentId, bound };
}

/**
 * authored components are linked only when they were exported before
 */
function _linkAuthoredComponents(consumer, component, componentMap) {
  var componentId = component.id;
  if (!componentId.scope) return { id: componentId, bound: [] }; // scope is a must to generate the link
  var filesToBind = componentMap.getFilesRelativeToConsumer();
  var bound = filesToBind.map(function (file) {
    var dest = (_path || _load_path()).default.join((_consumer || _load_consumer()).default.getNodeModulesPathOfComponent(component.bindingPrefix, componentId), file);
    var destRelative = (0, (_utils || _load_utils()).pathRelativeLinux)((_path || _load_path()).default.dirname(dest), file);
    var fileContent = `module.exports = require('${destRelative}');`;
    (_fsExtra || _load_fsExtra()).default.outputFileSync(dest, fileContent);
    return { from: dest, to: file };
  });
  linkToMainFile(component, componentMap, componentId, consumer);
  return { id: componentId, bound };
}

/**
 * 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)
 */

exports.default = function () {
  var _ref3 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3(components, consumer) {
    return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
      while (1) {
        switch (_context3.prev = _context3.next) {
          case 0:
            return _context3.abrupt('return', Promise.all(components.map(function (component) {
              var componentId = component.id;
              (_logger || _load_logger()).default.debug(`linking component to node_modules: ${componentId}`);
              var componentMap = consumer.bitMap.getComponent(componentId, true);
              switch (componentMap.origin) {
                case (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED:
                  return _linkImportedComponents(consumer, component, componentMap);
                case (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED:
                  return _linkNestedComponents(consumer, component, componentMap);
                case (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED:
                  return _linkAuthoredComponents(consumer, component, componentMap);
                default:
                  throw new Error(`ComponentMap.origin ${componentMap.origin} of ${componentId} is not recognized`);
              }
            })));

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

  function linkComponents(_x6, _x7) {
    return _ref3.apply(this, arguments);
  }

  return linkComponents;
}();