"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.GraphFromFsBuilder = void 0;
function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));
  _pMapSeries = function () {
    return data;
  };
  return data;
}
function _graph() {
  const data = require("@teambit/graph.cleargraph");
  _graph = function () {
    return data;
  };
  return data;
}
function _lodash() {
  const data = require("lodash");
  _lodash = function () {
    return data;
  };
  return data;
}
function _componentId() {
  const data = require("@teambit/component-id");
  _componentId = function () {
    return data;
  };
  return data;
}
function _exceptions() {
  const data = require("@teambit/legacy/dist/scope/exceptions");
  _exceptions = function () {
    return data;
  };
  return data;
}
function _scope() {
  const data = require("@teambit/scope");
  _scope = function () {
    return data;
  };
  return data;
}
function _lodash2() {
  const data = _interopRequireDefault(require("lodash.compact"));
  _lodash2 = function () {
    return data;
  };
  return data;
}
function _bitError() {
  const data = require("@teambit/bit-error");
  _bitError = function () {
    return data;
  };
  return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
class GraphFromFsBuilder {
  constructor(workspace, logger, dependencyResolver, ignoreIds = [], shouldLoadItsDeps, shouldThrowOnMissingDep = true) {
    this.workspace = workspace;
    this.logger = logger;
    this.dependencyResolver = dependencyResolver;
    this.ignoreIds = ignoreIds;
    this.shouldLoadItsDeps = shouldLoadItsDeps;
    this.shouldThrowOnMissingDep = shouldThrowOnMissingDep;
    _defineProperty(this, "graph", new (_graph().Graph)());
    _defineProperty(this, "completed", []);
    _defineProperty(this, "depth", 1);
    _defineProperty(this, "consumer", void 0);
    _defineProperty(this, "importedIds", []);
    _defineProperty(this, "currentLane", void 0);
    this.consumer = this.workspace.consumer;
  }

  /**
   * create a graph with all dependencies and flattened dependencies of the given components.
   * the nodes are components and the edges has a label of the dependency type.
   *
   * the way how it is done is iterations by depths. each depth we gather all the dependencies of
   * that depths, make sure all objects exist and then check their dependencies for the next depth.
   * once there is no dependency left, we're on the last depth level and the graph is ready.
   *
   * for example, imagine the following graph:
   * A1 -> A2 -> A3
   * B1 -> B2 -> B3
   * C1 -> C2 -> C3
   *
   * where the buildGraph is given [A1, B1, C1].
   * first, it saves all these components as nodes in the graph. then, it finds the dependencies of
   * the next level, in this case they're [A2, B2, C2]. it runs `importMany` in case some objects
   * are missing. then, it loads them all (some from FS, some from the model) and sets the edges
   * between the component and the dependencies.
   * once done, it finds all their dependencies, which are [A3, B3, C3] and repeat the process
   * above. since there are no more dependencies, the graph is completed.
   * in this case, the total depth levels are 3.
   *
   * even with a huge project, there are not many depth levels. by iterating through depth levels
   * we keep performance sane as the importMany doesn't run multiple time and therefore the round
   * trips to the remotes are minimal.
   *
   * normally, one importMany of the seeders is enough as importMany knows to fetch all flattened.
   * however, since this buildGraph is performed on the workspace, a dependency may be new or
   * modified and as such, we don't know its flattened yet.
   */
  async buildGraph(ids) {
    this.logger.debug(`GraphFromFsBuilder, buildGraph with ${ids.length} seeders`);
    const start = Date.now();
    const components = await this.loadManyComponents(ids);
    this.currentLane = await this.workspace.consumer.getCurrentLaneObject();
    await this.processManyComponents(components);
    this.logger.debug(`GraphFromFsBuilder, buildGraph with ${ids.length} seeders completed (${(Date.now() - start) / 1000} sec)`);
    return this.graph;
  }
  async getAllDepsUnfiltered(component) {
    const deps = await this.dependencyResolver.getComponentDependencies(component);
    const depsIds = deps.map(dep => dep.componentId);
    return depsIds.filter(depId => !this.ignoreIds.includes(depId.toString()));
  }
  async getAllDepsFiltered(component) {
    const depsWithoutIgnore = await this.getAllDepsUnfiltered(component);
    const shouldLoadFunc = this.shouldLoadItsDeps;
    if (!shouldLoadFunc) return depsWithoutIgnore;
    const deps = await (0, _pMapSeries().default)(depsWithoutIgnore, async depId => {
      const shouldLoad = await shouldLoadFunc(depId);
      if (!shouldLoad) this.ignoreIds.push(depId.toString());
      return shouldLoad ? depId : null;
    });
    return (0, _lodash2().default)(deps);
  }
  async processManyComponents(components) {
    this.logger.debug(`GraphFromFsBuilder.processManyComponents depth ${this.depth}, ${components.length} components`);
    this.depth += 1;
    await this.importObjects(components);
    const allDependencies = await (0, _pMapSeries().default)(components, component => this.processOneComponent(component));
    const allDependenciesFlattened = (0, _lodash().flatten)(allDependencies);
    if (allDependenciesFlattened.length) await this.processManyComponents(allDependenciesFlattened);
  }

  /**
   * only for components from the workspace that can be modified to add/remove dependencies, we need to make sure that
   * all their dependencies are imported.
   * remember that `importMany` fetches all flattened dependencies. once a component from scope is imported, we know
   * that all its flattened dependencies are there. no need to call importMany again for them.
   */
  async importObjects(components) {
    const workspaceIds = this.workspace.listIds();
    const compOnWorkspaceOnly = components.filter(comp => workspaceIds.find(id => id.isEqual(comp.id)));
    const allDeps = (await Promise.all(compOnWorkspaceOnly.map(c => this.getAllDepsUnfiltered(c)))).flat();
    const allDepsNotImported = allDeps.filter(d => !this.importedIds.includes(d.toString()));
    const exportedDeps = allDepsNotImported.map(id => id).filter(dep => this.workspace.isExported(dep));
    const scopeComponentsImporter = this.consumer.scope.scopeImporter;
    await scopeComponentsImporter.importMany({
      ids: _componentId().ComponentIdList.uniqFromArray(exportedDeps),
      preferDependencyGraph: false,
      throwForDependencyNotFound: this.shouldThrowOnMissingDep,
      throwForSeederNotFound: this.shouldThrowOnMissingDep,
      reFetchUnBuiltVersion: false,
      lane: this.currentLane,
      reason: 'for building a graph from the workspace'
    });
    allDepsNotImported.map(id => this.importedIds.push(id.toString()));
  }
  async processOneComponent(component) {
    const idStr = component.id.toString();
    if (this.completed.includes(idStr)) return [];
    const allIds = await this.getAllDepsFiltered(component);
    const allDependenciesComps = await this.loadManyComponents(allIds, idStr);
    const deps = await this.dependencyResolver.getComponentDependencies(component);
    deps.forEach(dep => {
      const depId = dep.componentId;
      if (this.ignoreIds.includes(depId.toString())) return;
      if (!this.graph.hasNode(depId.toString())) {
        if (this.shouldThrowOnMissingDep) {
          throw new Error(`buildOneComponent: missing node of ${depId.toString()}`);
        }
        this.logger.warn(`ignoring missing ${depId.toString()}`);
        return;
      }
      this.graph.setEdge(new (_graph().Edge)(idStr, depId.toString(), dep.lifecycle));
    });
    this.completed.push(idStr);
    return allDependenciesComps;
  }
  async loadManyComponents(componentsIds, dependenciesOf) {
    const components = await (0, _pMapSeries().default)(componentsIds, async comp => {
      const idStr = comp.toString();
      const fromGraph = this.graph.node(idStr)?.attr;
      if (fromGraph) return fromGraph;
      try {
        const component = await this.workspace.get(comp);
        this.graph.setNode(new (_graph().Node)(idStr, component));
        return component;
      } catch (err) {
        if (err instanceof _exceptions().ComponentNotFound || err instanceof _scope().ComponentNotFound || err instanceof _exceptions().ScopeNotFound) {
          if (dependenciesOf && !this.shouldThrowOnMissingDep) {
            this.logger.warn(`component ${idStr}, dependency of ${dependenciesOf} was not found. continuing without it`);
            return null;
          }
          throw new (_bitError().BitError)(`error: component "${idStr}" was not found.\nthis component is a dependency of "${dependenciesOf || '<none>'}" and is needed as part of the graph generation`);
        }
        if (dependenciesOf) this.logger.error(`failed loading dependencies of ${dependenciesOf}`);
        throw err;
      }
    });
    return (0, _lodash2().default)(components);
  }
}
exports.GraphFromFsBuilder = GraphFromFsBuilder;

//# sourceMappingURL=build-graph-from-fs.js.map