'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getLinksByDependencies = exports.getIndexFileName = exports.getComponentsDependenciesLinks = exports.getEntryPointsForComponent = undefined;

var _regenerator;

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

var _bluebird;

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

/**
 * used for writing compiler and tester dependencies to the directory of their configuration file
 * the configuration directory is not always the same as the component, it can be moved by 'eject-conf' command
 * this methods write the environment dependency links no matter where the directory located on the workspace
 *
 */
var getLinksByDependencies = function () {
  var _ref2 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(targetDir, component, dependencies, consumer) {
    var _this = this;

    var linksP, links, flattenLinks, _groupLinks2, linksToWrite;

    return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
      while (1) {
        switch (_context2.prev = _context2.next) {
          case 0:
            linksP = dependencies.get().map(function () {
              var _ref3 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(dependency) {
                var dependencyComponent, dependencyLinks;
                return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
                  while (1) {
                    switch (_context.prev = _context.next) {
                      case 0:
                        _context.next = 2;
                        return consumer.loadComponentFromModel(dependency.id);

                      case 2:
                        dependencyComponent = _context.sent;
                        dependencyLinks = dependency.relativePaths.map(function (relativePath) {
                          var dependencyFileLinkGenerator = new (_dependencyFileLinkGenerator || _load_dependencyFileLinkGenerator()).default({
                            consumer,
                            component,
                            relativePath,
                            dependencyComponent,
                            createNpmLinkFiles: false,
                            targetDir
                          });
                          return dependencyFileLinkGenerator.generate();
                        });
                        return _context.abrupt('return', (_ramda || _load_ramda()).default.flatten(dependencyLinks));

                      case 5:
                      case 'end':
                        return _context.stop();
                    }
                  }
                }, _callee, _this);
              }));

              return function (_x7) {
                return _ref3.apply(this, arguments);
              };
            }());
            _context2.next = 3;
            return Promise.all(linksP);

          case 3:
            links = _context2.sent;
            flattenLinks = (_ramda || _load_ramda()).default.flatten(links);
            _groupLinks2 = groupLinks(flattenLinks), linksToWrite = _groupLinks2.linksToWrite;
            // $FlowFixMe base is optional

            return _context2.abrupt('return', linksToWrite.map(function (link) {
              return (_linkFile || _load_linkFile()).default.load(link);
            }));

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

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

var _path;

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

var _ramda;

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

var _lodash;

function _load_lodash() {
  return _lodash = _interopRequireDefault(require('lodash.groupby'));
}

var _constants;

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

var _utils;

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

var _logger;

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

var _bitId;

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

var _componentMap;

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

var _postinstall;

function _load_postinstall() {
  return _postinstall = _interopRequireDefault(require('../consumer/component/templates/postinstall.default-template'));
}

var _linkContent;

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

var _dependencyFileLinkGenerator;

function _load_dependencyFileLinkGenerator() {
  return _dependencyFileLinkGenerator = _interopRequireDefault(require('./dependency-file-link-generator'));
}

var _linkFile;

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

var _bitMap;

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

var _jsonFile;

function _load_jsonFile() {
  return _jsonFile = _interopRequireDefault(require('../consumer/component/sources/json-file'));
}

var _dataToPersist;

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

var _componentIdToPackageName;

function _load_componentIdToPackageName() {
  return _componentIdToPackageName = _interopRequireDefault(require('../utils/bit/component-id-to-package-name'));
}

var _symlink;

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

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

// todo: move to bit-javascript
function getIndexFileName(mainFile) {
  return `${(_constants || _load_constants()).DEFAULT_INDEX_NAME}.${(0, (_utils || _load_utils()).getExt)(mainFile)}`;
}

/**
 * a component may have many dependencies, this function returns the links content for all of its dependencies
 */

function getComponentLinks(_ref) {
  var consumer = _ref.consumer,
      component = _ref.component,
      dependencies = _ref.dependencies,
      createNpmLinkFiles = _ref.createNpmLinkFiles,
      bitMap = _ref.bitMap;

  // $FlowFixMe
  bitMap = bitMap || consumer.bitMap;
  var componentMap = bitMap.getComponent(component.id);
  component.componentMap = componentMap;
  var directDependencies = _getDirectDependencies(component, componentMap);
  var flattenedDependencies = _getFlattenedDependencies(component, componentMap);
  var links = directDependencies.map(function (dep) {
    if (!dep.relativePaths || (_ramda || _load_ramda()).default.isEmpty(dep.relativePaths)) return [];
    var getDependencyIdWithResolvedVersion = function getDependencyIdWithResolvedVersion() {
      // Check if the dependency is latest, if yes we need to resolve if from the flatten dependencies to get the
      // Actual version number, because on the bitmap we have only specific versions
      if (dep.id.getVersion().latest) {
        return flattenedDependencies.resolveVersion(dep.id);
      }
      return dep.id;
    };
    var dependencyId = getDependencyIdWithResolvedVersion();
    var dependencyComponent = _getDependencyComponent(dependencyId, dependencies, component.id);

    var dependencyLinks = dep.relativePaths.map(function (relativePath) {
      var dependencyFileLinkGenerator = new (_dependencyFileLinkGenerator || _load_dependencyFileLinkGenerator()).default({
        // $FlowFixMe
        consumer,
        component,
        relativePath,
        dependencyComponent,
        createNpmLinkFiles
      });
      return dependencyFileLinkGenerator.generate();
    });
    return (_ramda || _load_ramda()).default.flatten(dependencyLinks);
  });
  var dataToPersist = new (_dataToPersist || _load_dataToPersist()).default();
  var internalCustomResolvedLinks = component.customResolvedPaths.length ? getInternalCustomResolvedLinks(component, componentMap, createNpmLinkFiles) : [];
  var flattenLinks = (_ramda || _load_ramda()).default.flatten(links).concat(internalCustomResolvedLinks);

  var _groupLinks = groupLinks(flattenLinks),
      postInstallLinks = _groupLinks.postInstallLinks,
      postInstallSymlinks = _groupLinks.postInstallSymlinks,
      linksToWrite = _groupLinks.linksToWrite,
      symlinks = _groupLinks.symlinks;

  var shouldGeneratePostInstallScript = postInstallLinks.length || postInstallSymlinks.length;
  if (shouldGeneratePostInstallScript) {
    var postInstallFile = generatePostInstallScript(component, postInstallLinks, postInstallSymlinks);
    dataToPersist.addFile(postInstallFile);
  }
  var customResolveAliasesAdded = addCustomResolveAliasesToPackageJson(component, flattenLinks);
  if (customResolveAliasesAdded || shouldGeneratePostInstallScript) {
    var packageJsonFile = _getPackageJsonFile(component);
    dataToPersist.addFile(packageJsonFile);
  }

  if (symlinks.length) {
    dataToPersist.addManySymlinks(symlinks.map(function (symlink) {
      return (_symlink || _load_symlink()).default.makeInstance(symlink.source, symlink.dest);
    }));
  }
  // $FlowFixMe
  dataToPersist.addManyFiles(linksToWrite.map(function (linkToWrite) {
    return (_linkFile || _load_linkFile()).default.load(linkToWrite);
  }));
  return dataToPersist;
}

function _getDependencyComponent(dependencyId, dependencies, componentId) {
  var componentWithSameVersion = dependencies.find(function (dependency) {
    return dependency.id.isEqual(dependencyId);
  });
  if (componentWithSameVersion) return componentWithSameVersion;
  var dependencyComponent = dependencies.find(function (dependency) {
    return dependency.id.isEqualWithoutVersion(dependencyId);
  });
  if (!dependencyComponent) {
    var errorMessage = `link-generation: failed finding ${dependencyId.toString()} in the dependencies array of ${componentId.toString()}.
The dependencies array has the following ids: ${dependencies.map(function (d) {
      return d.id;
    }).join(', ')}`;
    throw new Error(errorMessage);
  }
  (_logger || _load_logger()).default.warn(`link-generation: failed finding an exact version of ${dependencyId.toString()} in the dependencies array of ${componentId.toString()}.
    will use ${dependencyComponent.id.toString()} instead. this might happen when the dependency version is overridden in package.json or bit.json`);
  return dependencyComponent;
}

function _getPackageJsonFile(component) {
  // $FlowFixMe
  var componentDir = component.writtenPath;
  return (_jsonFile || _load_jsonFile()).default.load({
    base: componentDir,
    path: (_path || _load_path()).default.join(componentDir, 'package.json'),
    content: component.packageJsonInstance,
    override: true
  });
}

function _getDirectDependencies(component, componentMap) {
  // devDependencies of Nested components are not written to the filesystem, so no need to link them.
  return componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED ? component.dependencies.get() : component.getAllNonEnvsDependencies();
}

function _getFlattenedDependencies(component, componentMap) {
  return componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED ? component.flattenedDependencies : (_bitId || _load_bitId()).BitIds.fromArray(component.getAllNonEnvsFlattenedDependencies());
}

function groupLinks(flattenLinks) {
  var groupedLinks = (0, (_lodash || _load_lodash()).default)(flattenLinks, function (link) {
    return link.linkPath;
  });
  var linksToWrite = [];
  var postInstallLinks = [];
  var postInstallSymlinks = [];
  var symlinks = [];
  Object.keys(groupedLinks).forEach(function (group) {
    var content = '';
    var firstGroupItem = groupedLinks[group][0];
    if (firstGroupItem.symlinkTo) {
      if (firstGroupItem.postInstallSymlink) {
        postInstallSymlinks.push({ source: firstGroupItem.symlinkTo, dest: firstGroupItem.linkPath });
        return;
      }
      symlinks.push({ source: firstGroupItem.symlinkTo, dest: firstGroupItem.linkPath });
      return;
    }
    if (firstGroupItem.isEs6) {
      // check by the first item of the array, it can be any other item as well
      content = 'Object.defineProperty(exports, "__esModule", { value: true });\n';
    }
    content += groupedLinks[group].map(function (linkItem) {
      return linkItem.linkContent;
    }).join('\n');
    var linkFile = { filePath: group, content };
    if (firstGroupItem.postInstallLink) {
      postInstallLinks.push(linkFile);
    } else {
      linksToWrite.push(linkFile);
    }
  });
  return { postInstallLinks, postInstallSymlinks, linksToWrite, symlinks };
}

/**
 * The following scenario will help understanding why links are needed.
 * Component A has a dependency B. (for instance, in a.js there is a require statement to 'b.js').
 * While importing component A, it knows about the B dependency and it saves it under 'dependencies' directory of A.
 * The problem is that the above require is broken, because 'b.js' is not in the same place where it was originally.
 * This function solves this issue by creating the 'b.js' file in the original location and points to the new location
 * under 'dependencies' of A.
 *
 * It does the link generation in two steps.
 * step 1: "componentsLinks", it generates links to all imported components.
 * target: imported components. source: dependencies.
 * step 2: "dependenciesLinks", it generates links to all dependencies of the imported components.
 * target: dependencies. source: other dependencies.
 * this step is not needed when the imported components don't have dependencies, or when the
 * dependencies were installed as npm/yarn packages.
 */
function getComponentsDependenciesLinks(componentDependencies, consumer, createNpmLinkFiles, bitMap) {
  // $FlowFixMe
  bitMap = bitMap || consumer.bitMap;
  var componentsDependenciesLinks = new (_dataToPersist || _load_dataToPersist()).default();
  var linkedComponents = new (_bitId || _load_bitId()).BitIds();
  var componentsToLink = getComponentsToLink();
  addLinksForComponents();
  addLinksForDependencies();
  return componentsDependenciesLinks;
  function getComponentsToLink() {
    return componentDependencies.reduce(function (acc, componentWithDeps) {
      var component = componentWithDeps.component;
      // $FlowFixMe bitMap is set at this point
      var componentMap = bitMap.getComponent(component.id);
      component.componentMap = componentMap;
      if (componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
        (_logger || _load_logger()).default.debug(`writeComponentsDependenciesLinks, ignoring a component ${component.id.toString()} as it is an author component`);
        return acc;
      }
      return acc.concat(componentWithDeps);
    }, []);
  }
  function addLinksForComponents() {
    componentsToLink.forEach(function (componentWithDeps) {
      var component = componentWithDeps.component;
      if (linkedComponents.has(component.id)) return;
      // it must be IMPORTED. We don't pass NESTED to this function
      (_logger || _load_logger()).default.debug(`writeComponentsDependenciesLinks, generating links for ${component.id.toString()}`);
      var componentsLinks = getComponentLinks({
        consumer,
        component,
        dependencies: componentWithDeps.allDependencies,
        createNpmLinkFiles
      });
      componentsDependenciesLinks.merge(componentsLinks);
      linkedComponents.push(component.id);
    });
  }
  function addLinksForDependencies() {
    componentsToLink.forEach(function (componentWithDeps) {
      if (!componentWithDeps.component.dependenciesSavedAsComponents) return;
      componentWithDeps.allDependencies.forEach(function (dep) {
        if (linkedComponents.has(dep.id)) return;
        // We pass here the componentWithDeps.dependencies again because it contains the full dependencies objects
        // also the indirect ones
        // The dep.dependencies contain only an id and relativePaths and not the full object
        var dependencies = componentWithDeps.allDependencies;
        dependencies.push(componentWithDeps.component);
        var dependencyLinks = getComponentLinks({
          consumer,
          component: dep,
          dependencies,
          createNpmLinkFiles
        });
        componentsDependenciesLinks.merge(dependencyLinks);
        linkedComponents.push(dep.id);
      });
    });
  }
}

/**
 * important: do not attempt to move this function into DependencyFileLinkGenerator as it should be
 * running even when a component does not have any dependencies.
 *
 * when using custom module resolutions, and inside a component there is a file that requires
 * another file by custom-resolved syntax, we must generate links on the imported component inside
 * node_modules.
 *
 * E.g. original component "utils/jump" has two files:
 * bar/foo.js => require('utils/is-string); // "src" is set to be a module-directory
 * utils/is-string.js
 *
 * imported component:
 * components/utils/jump/bar/foo.js
 * components/utils/jump/utils/is-string.js
 * components/utils/jump/node_modules/utils/is-string // this is the file we generate here
 */
function getInternalCustomResolvedLinks(component, componentMap, createNpmLinkFiles) {
  var componentDir = component.writtenPath || componentMap.rootDir;
  if (!componentDir) {
    throw new Error(`getInternalCustomResolvedLinks, unable to find the written path of ${component.id.toString()}`);
  }
  var getDestination = function getDestination(importSource) {
    return `node_modules/${importSource}`;
  };
  return component.customResolvedPaths.map(function (customPath) {
    var sourceAbs = (_path || _load_path()).default.join(componentDir, customPath.destinationPath);
    var dest = getDestination(customPath.importSource);
    var destAbs = (_path || _load_path()).default.join(componentDir, dest);
    var destRelative = (_path || _load_path()).default.relative((_path || _load_path()).default.dirname(destAbs), sourceAbs);
    var linkContent = (0, (_linkContent || _load_linkContent()).getLinkToFileContent)(destRelative);
    var packageName = (0, (_componentIdToPackageName || _load_componentIdToPackageName()).default)(component.id, component.bindingPrefix);
    var customResolveMapping = { [customPath.importSource]: `${packageName}/${customPath.destinationPath}` };
    return {
      linkPath: createNpmLinkFiles ? dest : destAbs,
      linkContent,
      postInstallLink: createNpmLinkFiles,
      customResolveMapping
    };
  });
}

/**
 * change the package.json of a component to include postInstall script.
 * @see postInstallTemplate() JSDoc to understand better why this postInstall script is needed
 */
function generatePostInstallScript(component) {
  var postInstallLinks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  var postInstallSymlinks = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];

  // $FlowFixMe todo: is it possible that writtenPath is empty here?
  var componentDir = component.writtenPath;
  // convert from array to object for easier parsing in the postinstall script
  var linkPathsObject = postInstallLinks.reduce(function (acc, val) {
    acc[val.filePath] = val.content;
    return acc;
  }, {});
  var symlinkPathsObject = postInstallSymlinks.reduce(function (acc, val) {
    acc[val.dest] = val.source;
    return acc;
  }, {});
  if (!component.packageJsonInstance) throw new Error(`packageJsonInstance is missing for ${component.id.toString()}`);
  var postInstallCode = (0, (_postinstall || _load_postinstall()).default)(JSON.stringify(linkPathsObject), JSON.stringify(symlinkPathsObject));
  var POST_INSTALL_FILENAME = '.bit.postinstall.js';
  var postInstallFilePath = (_path || _load_path()).default.join(componentDir, POST_INSTALL_FILENAME);
  var postInstallScript = `node ${POST_INSTALL_FILENAME}`;
  // $FlowFixMe packageJsonInstance must be set
  component.packageJsonInstance.scripts = { postinstall: postInstallScript };
  var postInstallFile = (_linkFile || _load_linkFile()).default.load({ filePath: postInstallFilePath, content: postInstallCode, override: true });
  return postInstallFile;
}

function addCustomResolveAliasesToPackageJson(component, links) {
  var resolveAliases = links.reduce(function (acc, link) {
    if (link.customResolveMapping) Object.assign(acc, link.customResolveMapping);
    return acc;
  }, {});
  if ((_ramda || _load_ramda()).default.isEmpty(resolveAliases)) return false;
  // @TODO: load the package.json here if not found. it happens during the build process
  if (!component.packageJsonInstance) return false;
  if (component.packageJsonInstance.bit) component.packageJsonInstance.bit.resolveAliases = resolveAliases;else component.packageJsonInstance.bit = { resolveAliases };
  return true;
}

/**
 * Relevant for IMPORTED and NESTED only
 */
function getEntryPointsForComponent(component, consumer) {
  var files = [];
  var componentMap = consumer.bitMap.getComponent(component.id);
  // $FlowFixMe
  var componentRoot = component.writtenPath || componentMap.rootDir;
  if (componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) return [];
  var mainFile = component.dists.calculateMainDistFile(component.mainFile);
  var indexName = getIndexFileName(mainFile); // Move to bit-javascript
  var entryPointFileContent = (0, (_linkContent || _load_linkContent()).getLinkToFileContent)(`./${mainFile}`);
  var entryPointPath = (_path || _load_path()).default.join(componentRoot, indexName);
  if (!component.dists.isEmpty() && component.dists.writeDistsFiles && !consumer.shouldDistsBeInsideTheComponent()) {
    var distDir = component.dists.getDistDirForConsumer(consumer, componentRoot);
    var entryPointDist = (_path || _load_path()).default.join(distDir, indexName);
    (_logger || _load_logger()).default.debug(`writeEntryPointFile, on ${entryPointDist}`);
    files.push((_linkFile || _load_linkFile()).default.load({ filePath: entryPointDist, content: entryPointFileContent }));
  }
  (_logger || _load_logger()).default.debug(`writeEntryPointFile, on ${entryPointPath}`);
  files.push((_linkFile || _load_linkFile()).default.load({ filePath: entryPointPath, content: entryPointFileContent }));
  return files;
}exports.getEntryPointsForComponent = getEntryPointsForComponent;
exports.getComponentsDependenciesLinks = getComponentsDependenciesLinks;
exports.getIndexFileName = getIndexFileName;
exports.getLinksByDependencies = getLinksByDependencies;