"use strict";

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

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

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

function _ramda() {
  const data = _interopRequireDefault(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function _graphlib() {
  const data = _interopRequireWildcard(require("graphlib"));

  _graphlib = function () {
    return data;
  };

  return data;
}

function _bitId() {
  const data = require("../../bit-id");

  _bitId = function () {
    return data;
  };

  return data;
}

function _dependencies() {
  const data = require("../../consumer/component/dependencies");

  _dependencies = function () {
    return data;
  };

  return data;
}

const removeNils = _ramda().default.reject(_ramda().default.isNil);

/**
 * bumping dependencies version, so-called "auto tagging" is needed when the currently tagged
 * component has dependents. these dependents should have the updated version of the currently
 * tagged component.
 *
 * to successfully accomplish the auto-tag, we do it with two rounds.
 * it's easier to understand why another round of updateComponents() is needed by an example.
 * say we have 3 components, bar/foo@0.0.1 depends on utils/is-string, utils/is-string@0.0.1 depends on
 * utils/is-type, utils/is-type@0.0.1 with no dependencies.
 * when utils/is-type is tagged, utils/is-string and bar/foo are updated in the first updateComponents() round.
 * by looking at bar/foo dependencies, we find out that its utils/is-type dependency was updated to 0.0.2
 * however, its utils/is-string dependency stays with 0.0.1, because utils/is-string was never part of
 * taggedComponents array.
 * this second round of updateComponents() makes sure that the auto-tagged components will be updated as well.
 *
 * another case when round2 is needed is when the tagged component has a cycle dependency.
 * for example, A => B => C => A, and C is now tagged. the component C has the components A and B
 * in its flattenedDependencies.
 * Round1 updates A and B. It changes the C dependency to be 0.0.2 and bump their version to 0.0.2.
 * Round2 updates the dependencies and flattenedDependencies of C to have A and B with version 0.0.2.
 */
function bumpDependenciesVersions(_x, _x2, _x3) {
  return _bumpDependenciesVersions.apply(this, arguments);
}

function _bumpDependenciesVersions() {
  _bumpDependenciesVersions = (0, _bluebird().coroutine)(function* (scope, potentialComponents, taggedComponents) {
    const taggedComponentsIds = _bitId().BitIds.fromArray(taggedComponents.map(c => c.id));

    const allComponents = new (_bitId().BitIds)(...potentialComponents, ...taggedComponentsIds);
    const componentsAndVersions = yield scope.getComponentsAndVersions(allComponents);
    const graph = buildGraph(componentsAndVersions);
    const updatedComponents = yield updateComponents(componentsAndVersions, scope, taggedComponentsIds, taggedComponentsIds, false, graph);

    if (updatedComponents.length) {
      const ids = updatedComponents.map(({
        component
      }) => component.toBitIdWithLatestVersion());
      yield updateComponents(componentsAndVersions, scope, taggedComponentsIds, _bitId().BitIds.fromArray(ids), true, graph);
    }

    return updatedComponents;
  });
  return _bumpDependenciesVersions.apply(this, arguments);
}

function updateComponents(_x4, _x5, _x6, _x7) {
  return _updateComponents.apply(this, arguments);
}

function _updateComponents() {
  _updateComponents = (0, _bluebird().coroutine)(function* (componentsAndVersions, scope, taggedComponents, changedComponents, isRound2 = false, graph) {
    const autoTagResults = [];
    const componentsToUpdateP = componentsAndVersions.map(
    /*#__PURE__*/
    function () {
      var _ref = (0, _bluebird().coroutine)(function* ({
        component,
        version
      }) {
        let pendingUpdate = false;
        const bitId = component.toBitId();
        const idStr = bitId.toStringWithoutVersion();
        if (!graph.hasNode(idStr)) return null;
        const isCommittedComponent = taggedComponents.hasWithoutVersion(bitId);

        if (isCommittedComponent && !isRound2) {
          // if isCommittedComponent is true, the only case it's needed to be updated is when it has
          // cycle dependencies. in that case, it should be updated on the round2 only.
          return null;
        }

        const edges = _graphlib().default.alg.preorder(graph, idStr);

        const triggeredBy = new (_bitId().BitIds)();
        edges.forEach(edge => {
          const edgeId = graph.node(edge);
          const changedComponentId = changedComponents.searchWithoutVersion(edgeId);

          if (changedComponentId && _semver().default.gt(changedComponentId.version, edgeId.version)) {
            updateDependencies(version, edgeId, changedComponentId);
            pendingUpdate = true;
            triggeredBy.push(edgeId);
          }
        });

        if (pendingUpdate) {
          const message = isCommittedComponent ? version.log.message : 'bump dependencies versions';

          if (isRound2) {
            delete component.versions[component.latest()];
          }

          autoTagResults.push({
            component,
            triggeredBy
          });
          return scope.sources.putAdditionalVersion(component, version, message);
        }

        return null;
      });

      return function (_x11) {
        return _ref.apply(this, arguments);
      };
    }());
    yield Promise.all(componentsToUpdateP);
    return autoTagResults;
  });
  return _updateComponents.apply(this, arguments);
}

function updateDependencies(version, edgeId, changedComponentId) {
  version.updateFlattenedDependency(edgeId, edgeId.changeVersion(changedComponentId.version));
  const dependencyToUpdate = version.getAllDependencies().find(dependency => dependency.id.isEqualWithoutVersion(edgeId));

  if (dependencyToUpdate) {
    // it's a direct dependency
    dependencyToUpdate.id = dependencyToUpdate.id.changeVersion(changedComponentId.version);
  }
}

function buildGraph(componentsAndVersions) {
  const graph = new (_graphlib().Graph)();

  const componentsIds = _bitId().BitIds.fromArray(componentsAndVersions.map(c => c.component.toBitId()));

  componentsAndVersions.forEach(({
    component,
    version,
    versionStr
  }) => {
    const id = component.id();
    version.getAllDependencies().forEach(dependency => {
      if (componentsIds.searchWithoutVersion(dependency.id)) {
        const depId = dependency.id.toStringWithoutVersion(); // save the full BitId of a string id to be able to retrieve it later with no confusion

        if (!graph.hasNode(id)) graph.setNode(id, component.toBitId().changeVersion(versionStr));
        if (!graph.hasNode(depId)) graph.setNode(depId, dependency.id);
        graph.setEdge(id, depId);
      }
    });
  });
  return graph;
}

function getAutoTagPending(_x8, _x9, _x10) {
  return _getAutoTagPending.apply(this, arguments);
}

function _getAutoTagPending() {
  _getAutoTagPending = (0, _bluebird().coroutine)(function* (scope, potentialComponents, changedComponents) {
    const componentsAndVersions = yield scope.getComponentsAndVersions(new (_bitId().BitIds)(...potentialComponents, ...changedComponents));
    const graph = buildGraph(componentsAndVersions);
    const autoTagPendingComponents = componentsAndVersions.map(({
      component
    }) => {
      const bitId = component.toBitId();
      const idStr = bitId.toStringWithoutVersion();
      if (!graph.hasNode(idStr)) return null;

      const edges = _graphlib().default.alg.preorder(graph, idStr);

      const isAutoTagPending = edges.some(edge => {
        const edgeId = graph.node(edge);
        const changedComponentId = changedComponents.searchWithoutVersion(edgeId);
        if (!changedComponentId) return false;
        if (changedComponents.searchWithoutVersion(bitId)) return false; // we only check whether a modified component may cause auto-tagging
        // since it's only modified on the file-system, its version might be the same as the version stored in its
        // dependents. That's why "semver.gte" is used instead of "semver.gt".

        return _semver().default.gte(changedComponentId.version, edgeId.version);
      });
      return isAutoTagPending ? component : null;
    });
    return removeNils(autoTagPendingComponents);
  });
  return _getAutoTagPending.apply(this, arguments);
}