"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

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

  _bluebird = function () {
    return data;
  };

  return data;
}

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

  _graphlib = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("../../constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _scope() {
  const data = _interopRequireDefault(require("../scope"));

  _scope = function () {
    return data;
  };

  return data;
}

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

  _dependencies = function () {
    return data;
  };

  return data;
}

function _consumerComponent() {
  const data = _interopRequireDefault(require("../../consumer/component/consumer-component"));

  _consumerComponent = function () {
    return data;
  };

  return data;
}

const Graph = _graphlib().default.Graph;

class DependencyGraph {
  constructor(graph) {
    this.graph = graph;
  }

  static loadAllVersions(scope) {
    return (0, _bluebird().coroutine)(function* () {
      const graph = yield DependencyGraph.buildGraphWithAllVersions(scope);
      return new DependencyGraph(graph);
    })();
  }

  static loadLatest(scope) {
    return (0, _bluebird().coroutine)(function* () {
      const graph = yield DependencyGraph.buildGraphOfLatestFromScope(scope);
      return new DependencyGraph(graph);
    })();
  }

  static loadFromString(str) {
    const graph = _graphlib().default.json.read(str); // when getting a graph from a remote scope, the class BitId is gone and only the object is received


    graph.nodes().forEach(node => {
      const id = graph.node(node);

      if (!(id instanceof _bitId().BitId)) {
        graph.setNode(node, new (_bitId().BitId)(id));
      }
    });
    return new DependencyGraph(graph);
  }

  static buildGraphWithAllVersions(scope) {
    return (0, _bluebird().coroutine)(function* () {
      const graph = new Graph({
        compound: true
      });
      const depObj = {}; // $FlowFixMe

      const allComponents = yield scope.list(); // build all nodes. a node is either a Version object or Component object.
      // each Version node has a parent of Component node. Component node doesn't have a parent.

      yield Promise.all(allComponents.map(
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* (component) {
          graph.setNode(component.id(), component);
          yield Promise.all(Object.keys(component.versions).map(
          /*#__PURE__*/
          function () {
            var _ref2 = (0, _bluebird().coroutine)(function* (version) {
              const componentVersion = yield component.loadVersion(version, scope.objects);
              if (!componentVersion) return;
              const idWithVersion = `${component.id()}${_constants().VERSION_DELIMITER}${version}`;
              graph.setNode(idWithVersion, componentVersion);
              graph.setParent(idWithVersion, component.id());
              componentVersion.id = component.toBitId();
              depObj[idWithVersion] = componentVersion;
            });

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

        return function (_x) {
          return _ref.apply(this, arguments);
        };
      }())); // set all edges
      // @todo: currently the label is "require". Change it to be "direct" and "indirect" depends on whether it comes from
      // flattenedDependencies or from dependencies.

      Object.keys(depObj).forEach(id => depObj[id].flattenedDependencies.forEach(dep => graph.setEdge(id, dep.toString(), 'require')));
      return graph;
    })();
  }

  static buildGraphOfLatestFromScope(scope) {
    return (0, _bluebird().coroutine)(function* () {
      const graph = new Graph();
      const allModelComponents = yield scope.list();
      const buildGraphP = allModelComponents.map(
      /*#__PURE__*/
      function () {
        var _ref3 = (0, _bluebird().coroutine)(function* (modelComponent) {
          // $FlowFixMe
          const latestVersion = yield modelComponent.loadVersion(modelComponent.latest(), scope.objects);
          const id = modelComponent.toBitIdWithLatestVersion();
          const idStr = id.toString(); // save the full BitId of a string id to be able to retrieve it later with no confusion

          if (!graph.hasNode(idStr)) graph.setNode(idStr, id);

          _dependencies().DEPENDENCIES_TYPES.forEach(depType => {
            latestVersion[depType].get().forEach(dependency => {
              const depIdStr = dependency.id.toString();
              if (!graph.hasNode(depIdStr)) graph.setNode(depIdStr, dependency.id);
              graph.setEdge(idStr, depIdStr, depType);
            });
          });
        });

        return function (_x3) {
          return _ref3.apply(this, arguments);
        };
      }());
      yield Promise.all(buildGraphP);
      return graph;
    })();
  }

  static buildGraphFromComponents(components) {
    const graph = new Graph();
    components.forEach(component => {
      const id = component.id;
      const idStr = component.id.toString(); // save the full BitId of a string id to be able to retrieve it later with no confusion

      if (!graph.hasNode(idStr)) graph.setNode(idStr, id);

      _dependencies().DEPENDENCIES_TYPES.forEach(depType => {
        // $FlowFixMe
        component[depType].get().forEach(dependency => {
          const depIdStr = dependency.id.toString();
          if (!graph.hasNode(depIdStr)) graph.setNode(depIdStr, dependency.id);
          graph.setEdge(idStr, depIdStr, depType);
        });
      });
    });
    return graph;
  }
  /**
   * returns a new Graph that has only nodes that are related to the given id.
   * (meaning, they're either dependents or dependencies)
   */


  getSubGraphOfConnectedComponents(id) {
    const connectedGraphs = _graphlib().default.alg.components(this.graph);

    const graphWithId = connectedGraphs.find(graph => graph.includes(id.toString()));

    if (!graphWithId) {
      throw new Error(`${id.toString()} is missing from the dependency graph`);
    }

    return this.graph.filterNodes(node => graphWithId.includes(node));
  }

  getDependenciesInfo(id) {
    const dijkstraResults = _graphlib().default.alg.dijkstra(this.graph, id.toString());

    const dependencies = [];
    Object.keys(dijkstraResults).forEach(idStr => {
      const distance = dijkstraResults[idStr].distance;

      if (distance === Infinity || distance === 0) {
        // there is no dependency or it's the same component (distance zero)
        return;
      }

      const predecessor = dijkstraResults[idStr].predecessor;
      const dependencyType = this.graph.edge(predecessor, idStr);
      dependencies.push({
        id: this.graph.node(idStr),
        depth: distance,
        parent: predecessor,
        dependencyType: _dependencies().DEPENDENCIES_TYPES_UI_MAP[dependencyType]
      });
    });
    dependencies.sort((a, b) => a.depth - b.depth);
    return dependencies;
  }

  getDependentsInfo(id) {
    const edgeFunc = v => this.graph.inEdges(v);

    const dijkstraResults = _graphlib().default.alg.dijkstra(this.graph, id.toString(), undefined, edgeFunc);

    const dependents = [];
    Object.keys(dijkstraResults).forEach(idStr => {
      const distance = dijkstraResults[idStr].distance;

      if (distance === Infinity || distance === 0) {
        // there is no dependency or it's the same component (distance zero)
        return;
      }

      const predecessor = dijkstraResults[idStr].predecessor;
      const dependencyType = this.graph.edge(idStr, predecessor);
      dependents.push({
        id: this.graph.node(idStr),
        depth: distance,
        parent: predecessor,
        dependencyType: _dependencies().DEPENDENCIES_TYPES_UI_MAP[dependencyType]
      });
    });
    dependents.sort((a, b) => a.depth - b.depth);
    return dependents;
  }

  getComponent(id) {
    return this.graph.node(id.toStringWithoutVersion());
  }

  getDependentsPerId(id) {
    const nodeEdges = this.graph.inEdges(id.toString());
    if (!nodeEdges) return [];
    return nodeEdges.map(node => node.v);
  }

  getDependenciesPerId(id) {
    const nodeEdges = this.graph.outEdges(id.toString());
    if (!nodeEdges) return [];
    return nodeEdges.map(node => node.v);
  }

  serialize(graph = this.graph) {
    return _graphlib().default.json.write(graph);
  }

}

exports.default = DependencyGraph;