"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.GraphIdsFromFsBuilder = void 0;
exports.lifecycleToDepType = lifecycleToDepType;
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 _component() {
  const data = _interopRequireDefault(require("@teambit/legacy/dist/consumer/component"));
  _component = 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); }
function lifecycleToDepType(compDep) {
  if (compDep.isExtension) return 'ext';
  switch (compDep.lifecycle) {
    case 'dev':
      return 'dev';
    case 'runtime':
      return 'prod';
    case 'peer':
      return 'peer';
    default:
      throw new Error(`lifecycle ${compDep.lifecycle} is not support`);
  }
}
class GraphIdsFromFsBuilder {
  // for now it has the same value as shouldThrowOnMissingDep. change if needed
  constructor(workspace, logger, dependencyResolver, shouldThrowOnMissingDep = true) {
    this.workspace = workspace;
    this.logger = logger;
    this.dependencyResolver = dependencyResolver;
    this.shouldThrowOnMissingDep = shouldThrowOnMissingDep;
    _defineProperty(this, "graph", new (_graph().Graph)());
    _defineProperty(this, "completed", []);
    _defineProperty(this, "depth", 1);
    _defineProperty(this, "consumer", void 0);
    _defineProperty(this, "loadedComponents", {});
    _defineProperty(this, "importedIds", []);
    _defineProperty(this, "shouldThrowOnInvalidDeps", true);
    this.consumer = this.workspace.consumer;
    this.shouldThrowOnInvalidDeps = this.shouldThrowOnMissingDep;
  }

  /**
   * create a graph with all dependencies and flattened dependencies of the given components.
   * the nodes are component-ids and the edges has a label of the dependency type.
   * to get some info about this the graph build take a look into build-graph-from-fs.buildGraph() docs.
   */
  async buildGraph(ids) {
    this.logger.debug(`GraphIdsFromFsBuilder, buildGraph with ${ids.length} seeders`);
    const start = Date.now();
    const components = await this.loadManyComponents(ids);
    await this.processManyComponents(components);
    this.logger.debug(`GraphIdsFromFsBuilder, buildGraph with ${ids.length} seeders completed (${(Date.now() - start) / 1000} sec)`);
    return this.graph;
  }
  async processManyComponents(components) {
    this.logger.debug(`GraphIdsFromFsBuilder.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.
   * once a component from scope is imported, we know that either we have its dependency graph or all flattened deps
   */
  async importObjects(components) {
    const workspaceIds = this.workspace.listIds();
    const compOnWorkspaceOnly = components.filter(comp => workspaceIds.find(id => id.isEqual(comp.id)));
    const notImported = compOnWorkspaceOnly.map(c => c.id).filter(id => !this.importedIds.includes(id.toString()));
    const exportedDeps = notImported.filter(dep => this.workspace.isExported(dep));
    const scopeComponentsImporter = this.consumer.scope.scopeImporter;
    await scopeComponentsImporter.importMany({
      ids: _componentId().ComponentIdList.uniqFromArray(exportedDeps),
      throwForDependencyNotFound: this.shouldThrowOnMissingDep,
      throwForSeederNotFound: this.shouldThrowOnMissingDep,
      reFetchUnBuiltVersion: false,
      lane: await this.workspace.getCurrentLaneObject(),
      reason: 'for building graph-ids from the workspace'
    });
    notImported.map(id => this.importedIds.push(id.toString()));
  }
  async processOneComponent(component) {
    const idStr = component.id.toString();
    if (this.completed.includes(idStr)) return [];
    const graphFromScope = await this.workspace.getSavedGraphOfComponentIfExist(component);
    if (graphFromScope?.edges.length) {
      const isOnWorkspace = await this.workspace.hasId(component.id);
      if (isOnWorkspace) {
        const allDependenciesComps = await this.processCompFromWorkspaceWithGraph(graphFromScope, component);
        this.completed.push(idStr);
        return allDependenciesComps;
      }
      this.graph.merge([graphFromScope]);
      this.completed.push(idStr);
      return [];
    }
    const deps = this.dependencyResolver.getComponentDependencies(component);
    const allDepsIds = deps.map(d => d.componentId);
    const allDependenciesComps = await this.loadManyComponents(allDepsIds, idStr);
    deps.forEach(dep => this.addDepEdge(idStr, dep));
    this.completed.push(idStr);
    return allDependenciesComps;
  }

  /**
   * this is tricky.
   * the component is in the workspace so it can be modified. dependencies can be added/removed/updated/downgraded.
   * we have the graph-dependencies from the last snap, so we prefer to use it whenever possible for performance reasons.
   * if we can't use it, we have to recursively load dependencies components and get the data from there.
   * to maximize the performance, we iterate the direct dependencies, if we find a dep with the same id in the graph,
   * and that id is not in the workspace then ask the graph for all its successors. otherwise, if it's not there, or
   * it's there but it's also in the workspace (which therefore can be modified), we recursively load the dep components
   * and get its dependencies.
   */
  async processCompFromWorkspaceWithGraph(graphFromScope, component) {
    const deps = this.dependencyResolver.getComponentDependencies(component);
    const workspaceIds = this.workspace.listIds();
    const workspaceIdsStr = workspaceIds.map(id => id.toString());
    const [depsInScopeGraph, depsNotInScopeGraph] = (0, _lodash().partition)(deps, dep => graphFromScope.hasNode(dep.componentId.toString()) && !workspaceIdsStr.includes(dep.componentId.toString()));
    const depsInScopeGraphIds = depsInScopeGraph.map(dep => dep.componentId.toString());
    const depsInScopeGraphIdsNotCompleted = depsInScopeGraphIds.filter(id => !this.completed.includes(id));
    if (depsInScopeGraphIdsNotCompleted.length) {
      const subGraphs = graphFromScope.successorsSubgraph(depsInScopeGraphIdsNotCompleted);
      // delete any edge that its source is from the workspace. if this component is modified, this edge could be
      // incorrect. we don't need these edges anyway because we add them directly.
      subGraphs.edges.forEach(edge => {
        if (workspaceIdsStr.includes(edge.sourceId)) subGraphs.deleteEdge(edge.sourceId, edge.targetId);
      });
      this.graph.merge([subGraphs]);
      this.completed.push(...depsInScopeGraphIdsNotCompleted);
    }
    const allDepsIds = depsNotInScopeGraph.map(d => d.componentId);
    const idStr = component.id.toString();
    const allDependenciesComps = await this.loadManyComponents(allDepsIds, idStr);
    deps.forEach(dep => this.addDepEdge(idStr, dep));
    return allDependenciesComps;
  }
  addDepEdge(idStr, dep) {
    const depId = dep.componentId;
    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(), lifecycleToDepType(dep)));
  }
  async loadManyComponents(componentsIds, dependenciesOf) {
    const components = await (0, _pMapSeries().default)(componentsIds, async comp => {
      const idStr = comp.toString();
      const fromCache = this.loadedComponents[idStr];
      if (fromCache) return fromCache;
      try {
        const component = await this.workspace.get(comp);
        this.loadedComponents[idStr] = component;
        this.graph.setNode(new (_graph().Node)(idStr, component.id));
        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 (_component().default.isComponentInvalidByErrorType(err)) {
          if (dependenciesOf && !this.shouldThrowOnInvalidDeps) {
            this.logger.warn(`component ${idStr}, dependency of ${dependenciesOf} is invalid. continuing without it`);
            return null;
          }
          throw new (_bitError().BitError)(`error: component "${idStr}" is invalid (${err.message}).\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.GraphIdsFromFsBuilder = GraphIdsFromFsBuilder;

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