"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.WorkspaceAspectsLoader = void 0;
function _constants() {
  const data = require("@teambit/legacy/dist/constants");
  _constants = function () {
    return data;
  };
  return data;
}
function _findRoot() {
  const data = _interopRequireDefault(require("find-root"));
  _findRoot = function () {
    return data;
  };
  return data;
}
function _toolboxModules() {
  const data = require("@teambit/toolbox.modules.module-resolver");
  _toolboxModules = function () {
    return data;
  };
  return data;
}
function _aspectLoader() {
  const data = require("@teambit/aspect-loader");
  _aspectLoader = function () {
    return data;
  };
  return data;
}
function _cli() {
  const data = require("@teambit/cli");
  _cli = function () {
    return data;
  };
  return data;
}
function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));
  _fsExtra = function () {
    return data;
  };
  return data;
}
function _harmonyModules() {
  const data = require("@teambit/harmony.modules.requireable-component");
  _harmonyModules = function () {
    return data;
  };
  return data;
}
function _workspaceModules() {
  const data = require("@teambit/workspace.modules.node-modules-linker");
  _workspaceModules = 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 _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));
  _pMapSeries = function () {
    return data;
  };
  return data;
}
function _lodash() {
  const data = require("lodash");
  _lodash = 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 ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return 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 WorkspaceAspectsLoader {
  constructor(workspace, scope, aspectLoader, envs, dependencyResolver, logger, globalConfig, harmony, onAspectsResolveSlot, onRootAspectAddedSlot, resolveAspectsFromNodeModules = false, resolveEnvsFromRoots = false) {
    this.workspace = workspace;
    this.scope = scope;
    this.aspectLoader = aspectLoader;
    this.envs = envs;
    this.dependencyResolver = dependencyResolver;
    this.logger = logger;
    this.globalConfig = globalConfig;
    this.harmony = harmony;
    this.onAspectsResolveSlot = onAspectsResolveSlot;
    this.onRootAspectAddedSlot = onRootAspectAddedSlot;
    this.resolveAspectsFromNodeModules = resolveAspectsFromNodeModules;
    this.resolveEnvsFromRoots = resolveEnvsFromRoots;
    _defineProperty(this, "consumer", void 0);
    _defineProperty(this, "resolvedInstalledAspects", void 0);
    this.consumer = this.workspace.consumer;
    this.resolvedInstalledAspects = new Map();
    // Only enable this when root components is enabled as well
    this.resolveEnvsFromRoots = this.resolveEnvsFromRoots && this.dependencyResolver.hasRootComponents();
  }

  /**
   * load aspects from the workspace and if not exists in the workspace, load from the node_modules.
   * keep in mind that the graph may have circles.
   */
  async loadAspects(ids = [], throwOnError, neededFor, opts = {}) {
    const calculatedThrowOnError = throwOnError ?? false;
    const defaultOpts = {
      useScopeAspectsCapsule: false,
      throwOnError: calculatedThrowOnError,
      runSubscribers: true,
      skipDeps: false,
      hideMissingModuleError: !!this.workspace.inInstallContext,
      ignoreErrors: false,
      resolveEnvsFromRoots: this.resolveEnvsFromRoots,
      forceLoad: false
    };
    const mergedOpts = _objectSpread(_objectSpread({}, defaultOpts), opts);

    // generate a random callId to be able to identify the call from the logs
    const callId = Math.floor(Math.random() * 1000);
    const loggerPrefix = `[${callId}] loadAspects,`;
    this.logger.profile(`[${callId}] workspace.loadAspects`);
    this.logger.info(`${loggerPrefix} loading ${ids.length} aspects.
ids: ${ids.join(', ')}
needed-for: ${neededFor || '<unknown>'}. using opts: ${JSON.stringify(mergedOpts, null, 2)}`);
    const [localAspects, nonLocalAspects] = (0, _lodash().partition)(ids, id => id.startsWith('file:'));
    const localAspectsMap = await this.aspectLoader.loadAspectFromPath(localAspects);
    this.workspace.localAspects = localAspectsMap;
    let notLoadedIds = nonLocalAspects;
    if (!mergedOpts.forceLoad) {
      notLoadedIds = nonLocalAspects.filter(id => !this.aspectLoader.isAspectLoaded(id));
    }
    if (!notLoadedIds.length) {
      this.logger.profile(`[${callId}] workspace.loadAspects`);
      return [];
    }
    const coreAspectsStringIds = this.aspectLoader.getCoreAspectIds();
    const idsWithoutCore = (0, _lodash().difference)(notLoadedIds, coreAspectsStringIds);
    const componentIds = await this.workspace.resolveMultipleComponentIds(idsWithoutCore);
    const {
      workspaceIds,
      nonWorkspaceIds
    } = await this.groupIdsByWorkspaceExistence(componentIds, mergedOpts.resolveEnvsFromRoots);
    this.logFoundWorkspaceVsScope(loggerPrefix, workspaceIds, nonWorkspaceIds);
    let idsToLoadFromWs = componentIds;
    let scopeAspectIds = [];

    // TODO: hard coded use the old approach and loading from the scope capsules
    // This is because right now loading from the ws node_modules causes issues in some cases
    // like for the cloud app
    // it should be removed once we fix the issues
    if (!this.resolveAspectsFromNodeModules) {
      mergedOpts.useScopeAspectsCapsule = true;
    }
    if (mergedOpts.useScopeAspectsCapsule) {
      idsToLoadFromWs = workspaceIds;
      scopeAspectIds = await this.loadFromScopeAspectsCapsule(nonWorkspaceIds, throwOnError, neededFor);
    }
    const aspectsDefs = await this.resolveAspects(undefined, idsToLoadFromWs, _objectSpread({
      excludeCore: true,
      requestedOnly: false
    }, mergedOpts));
    const {
      manifests,
      requireableComponents
    } = await this.loadAspectDefsByOrder(aspectsDefs, idsWithoutCore, mergedOpts.throwOnError, mergedOpts.hideMissingModuleError, neededFor, mergedOpts.runSubscribers);
    const potentialPluginsIndexes = (0, _lodash().compact)(manifests.map((manifest, index) => {
      if (this.aspectLoader.isValidAspect(manifest)) return undefined;
      return index;
    }));

    // Try require components for potential plugins
    const pluginsRequireableComponents = potentialPluginsIndexes.map(index => {
      return requireableComponents[index];
    });
    // Do the require again now that the plugins defs already registered
    const pluginsManifests = await this.aspectLoader.getManifestsFromRequireableExtensions(pluginsRequireableComponents, throwOnError, opts.runSubscribers);
    await this.aspectLoader.loadExtensionsByManifests(pluginsManifests, undefined, {
      throwOnError
    });
    const manifestIds = manifests.map(manifest => manifest.id);
    this.logger.debug(`${loggerPrefix} finish loading aspects`);
    this.logger.profile(`[${callId}] workspace.loadAspects`);
    return (0, _lodash().compact)(manifestIds.concat(scopeAspectIds));
  }
  async loadFromScopeAspectsCapsule(ids, throwOnError, neededFor) {
    let scopeAspectIds = [];
    const currentLane = await this.consumer.getCurrentLaneObject();
    if (!ids.length) return [];
    const nonWorkspaceIdsString = ids.map(id => id.toString());
    try {
      scopeAspectIds = await this.scope.loadAspects(nonWorkspaceIdsString, throwOnError, neededFor, currentLane, {
        packageManagerConfigRootDir: this.workspace.path,
        workspaceName: this.workspace.name
      });
      return scopeAspectIds;
    } catch (err) {
      this.throwWsJsoncAspectNotFoundError(err);
      return scopeAspectIds;
      throw err;
    }
  }
  throwWsJsoncAspectNotFoundError(err) {
    if (err instanceof _exceptions().ComponentNotFound) {
      const config = this.harmony.get('teambit.harmony/config');
      const configStr = JSON.stringify(config.workspaceConfig?.raw || {});
      if (configStr.includes(err.id)) {
        throw new (_bitError().BitError)(`error: a component "${err.id}" was not found
your workspace.jsonc has this component-id set. you might want to remove/change it.`);
      }
    }
  }
  async loadAspectDefsByOrder(aspectsDefs, seeders, throwOnError, hideMissingModuleError, neededFor, runSubscribers = true) {
    const {
      nonWorkspaceDefs
    } = await this.groupAspectDefsByWorkspaceExistence(aspectsDefs);
    const scopeAspectsLoader = this.scope.getScopeAspectsLoader();
    const scopeIds = (0, _lodash().compact)(nonWorkspaceDefs.map(aspectDef => aspectDef.getId));
    const scopeIdsGrouped = await scopeAspectsLoader.groupAspectIdsByEnvOfTheList(scopeIds);

    // Make sure to first load envs from the list otherwise it will fail when trying to load other aspects
    // as their envs might not be loaded yet
    if (scopeIdsGrouped.envs && scopeIdsGrouped.envs.length && !runSubscribers) {
      await this.scope.loadAspects(scopeIdsGrouped.envs, throwOnError, 'workspace.loadAspects loading scope aspects');
    }
    const requireableComponents = this.aspectDefsToRequireableComponents(aspectsDefs);
    const manifests = await this.aspectLoader.getManifestsFromRequireableExtensions(requireableComponents, throwOnError, runSubscribers);
    await this.aspectLoader.loadExtensionsByManifests(manifests, {
      seeders,
      neededFor
    }, {
      throwOnError,
      hideMissingModuleError
    });
    return {
      manifests,
      requireableComponents
    };
  }
  async resolveAspects(runtimeName, componentIds, opts) {
    const callId = Math.floor(Math.random() * 1000);
    const loggerPrefix = `[${callId}] workspace resolveAspects,`;
    this.logger.debug(`${loggerPrefix}, resolving aspects for - runtimeName: ${runtimeName}, componentIds: ${componentIds}`);
    const defaultOpts = {
      excludeCore: false,
      requestedOnly: false,
      filterByRuntime: true,
      useScopeAspectsCapsule: false,
      workspaceName: this.workspace.name,
      resolveEnvsFromRoots: this.resolveEnvsFromRoots,
      packageManagerConfigRootDir: this.workspace.path
    };
    const mergedOpts = _objectSpread(_objectSpread({}, defaultOpts), opts);
    const idsToResolve = componentIds ? componentIds.map(id => id.toString()) : this.harmony.extensionsIds;
    const workspaceLocalAspectsIds = Object.keys(this.workspace.localAspects);
    const [localAspectsIds, nonLocalAspectsIds] = (0, _lodash().partition)(idsToResolve, id => workspaceLocalAspectsIds.includes(id));
    const localDefs = await this.aspectLoader.resolveLocalAspects(localAspectsIds.map(id => this.workspace.localAspects[id]), runtimeName);
    const coreAspectsIds = this.aspectLoader.getCoreAspectIds();
    const configuredAspects = this.aspectLoader.getConfiguredAspects();
    // it's possible that componentIds are core-aspects that got version for some reason, remove the version to
    // correctly filter them out later.
    const userAspectsIds = nonLocalAspectsIds ? nonLocalAspectsIds.filter(id => !coreAspectsIds.includes(id.split('@')[0])).map(id => id.toString()) : (0, _lodash().difference)(this.harmony.extensionsIds, coreAspectsIds);
    const rootAspectsIds = (0, _lodash().difference)(configuredAspects, coreAspectsIds);
    const componentIdsToResolve = await this.workspace.resolveMultipleComponentIds(userAspectsIds);
    const components = await this.importAndGetAspects(componentIdsToResolve, opts?.throwOnError);
    // Run the on load slot
    await this.runOnAspectsResolveFunctions(components);
    if (opts?.skipDeps) {
      const wsAspectDefs = await this.aspectLoader.resolveAspects(components, this.getWorkspaceAspectResolver([], runtimeName));
      const coreAspectDefs = await Promise.all(coreAspectsIds.map(async coreId => {
        const rawDef = await (0, _aspectLoader().getAspectDef)(coreId, runtimeName);
        return this.aspectLoader.loadDefinition(rawDef);
      }));
      const idsToFilter = idsToResolve.map(idStr => _componentId().ComponentID.fromString(idStr));
      const targetDefs = wsAspectDefs.concat(coreAspectDefs).concat(localDefs);
      const finalDefs = this.aspectLoader.filterAspectDefs(targetDefs, idsToFilter, runtimeName, mergedOpts);
      return finalDefs;
    }
    const groupedByIsPlugin = (0, _lodash().groupBy)(components, component => {
      return this.aspectLoader.hasPluginFiles(component);
    });
    const graph = await this.getAspectsGraphWithoutCore(groupedByIsPlugin.false, this.isAspect.bind(this));
    const aspectsComponents = graph.nodes.map(node => node.attr).concat(groupedByIsPlugin.true || []);
    this.logger.debug(`${loggerPrefix} found ${aspectsComponents.length} aspects in the aspects-graph`);
    const {
      workspaceComps,
      nonWorkspaceComps
    } = await this.groupComponentsByWorkspaceExistence(aspectsComponents, mergedOpts.resolveEnvsFromRoots);
    const workspaceCompsIds = workspaceComps.map(c => c.id);
    const nonWorkspaceCompsIds = nonWorkspaceComps.map(c => c.id);
    this.logFoundWorkspaceVsScope(loggerPrefix, workspaceCompsIds, nonWorkspaceCompsIds);
    const stringIds = [];
    const wsAspectDefs = await this.aspectLoader.resolveAspects(workspaceComps, this.getWorkspaceAspectResolver(stringIds, runtimeName));
    await this.linkIfMissingWorkspaceAspects(wsAspectDefs);

    // TODO: hard coded use the old approach and loading from the scope capsules
    // This is because right now loading from the ws node_modules causes issues in some cases
    // like for the cloud app
    // it should be removed once we fix the issues
    if (!this.resolveAspectsFromNodeModules) {
      mergedOpts.useScopeAspectsCapsule = true;
    }
    let componentsToResolveFromScope = nonWorkspaceComps;
    let componentsToResolveFromInstalled = [];
    if (!mergedOpts.useScopeAspectsCapsule) {
      const nonWorkspaceCompsGroups = (0, _lodash().groupBy)(nonWorkspaceComps, component => this.envs.isEnv(component));
      componentsToResolveFromScope = nonWorkspaceCompsGroups.true || [];
      componentsToResolveFromInstalled = nonWorkspaceCompsGroups.false || [];
    }
    const scopeIds = componentsToResolveFromScope.map(c => c.id);
    this.logger.debug(`${loggerPrefix} ${scopeIds.length} components are not in the workspace and are loaded from the scope capsules:\n${scopeIds.map(id => id.toString()).join('\n')}`);
    const scopeAspectsDefs = scopeIds.length ? await this.scope.resolveAspects(runtimeName, scopeIds, mergedOpts) : [];
    this.logger.debug(`${loggerPrefix} ${componentsToResolveFromInstalled.length} components are not in the workspace and are loaded from the node_modules:\n${componentsToResolveFromInstalled.map(c => c.id.toString()).join('\n')}`);
    const installedAspectsDefs = componentsToResolveFromInstalled.length ? await this.aspectLoader.resolveAspects(componentsToResolveFromInstalled, this.getInstalledAspectResolver(graph, rootAspectsIds, runtimeName, {
      throwOnError: opts?.throwOnError ?? false
    })) : [];
    let coreAspectDefs = await Promise.all(coreAspectsIds.map(async coreId => {
      const rawDef = await (0, _aspectLoader().getAspectDef)(coreId, runtimeName);
      return this.aspectLoader.loadDefinition(rawDef);
    }));

    // due to lack of workspace and scope runtimes. TODO: fix after adding them.
    if (runtimeName && mergedOpts.filterByRuntime) {
      coreAspectDefs = coreAspectDefs.filter(coreAspect => {
        return coreAspect.runtimePath;
      });
    }
    const localResolved = await this.aspectLoader.resolveLocalAspects(Object.keys(this.workspace.localAspects), runtimeName);
    const allDefsExceptLocal = [...wsAspectDefs, ...coreAspectDefs, ...scopeAspectsDefs, ...installedAspectsDefs];
    const withoutLocalAspects = allDefsExceptLocal.filter(aspectId => {
      return !localResolved.find(localAspect => {
        return localAspect.id === aspectId.component?.id?.toStringWithoutVersion();
      });
    });
    const allDefs = [...withoutLocalAspects, ...localResolved];
    const idsToFilter = idsToResolve.map(idStr => _componentId().ComponentID.fromString(idStr));
    const filteredDefs = this.aspectLoader.filterAspectDefs(allDefs, idsToFilter, runtimeName, mergedOpts);
    return filteredDefs;
  }
  shouldUseHashForCapsules() {
    return !this.globalConfig.getSync(_constants().CFG_CAPSULES_BUILD_COMPONENTS_BASE_DIR);
  }
  getCapsulePath() {
    const defaultPath = this.workspace.path;
    return this.globalConfig.getSync(_constants().CFG_CAPSULES_BUILD_COMPONENTS_BASE_DIR) || defaultPath;
  }
  logFoundWorkspaceVsScope(loggerPrefix, workspaceIds, nonWorkspaceIds) {
    const workspaceIdsStr = workspaceIds.length ? workspaceIds.map(id => id.toString()).join('\n') : '';
    const nonWorkspaceIdsStr = nonWorkspaceIds.length ? nonWorkspaceIds.map(id => id.toString()).join('\n') : '';
    this.logger.debug(`${loggerPrefix} found ${workspaceIds.length} components in the workspace, ${nonWorkspaceIds.length} not in the workspace`);
    if (workspaceIdsStr) this.logger.debug(`${loggerPrefix} workspace components:\n${workspaceIdsStr}`);
    if (nonWorkspaceIdsStr) this.logger.debug(`${loggerPrefix} non workspace components (loaded from the scope capsules or from the node_modules):\n${nonWorkspaceIdsStr}`);
  }
  async use(aspectIdStr) {
    let aspectId = await this.workspace.resolveComponentId(aspectIdStr);
    const inWs = await this.workspace.hasId(aspectId);
    let aspectIdToAdd = aspectId.toStringWithoutVersion();
    let aspectsComponent;
    // let aspectPackage;
    if (!inWs) {
      const aspectsComponents = await this.importAndGetAspects([aspectId]);
      if (aspectsComponents[0]) {
        aspectsComponent = aspectsComponents[0];
        aspectId = aspectsComponent.id;
        aspectIdToAdd = aspectId.toString();
      }
    }
    const config = this.harmony.get('teambit.harmony/config').workspaceConfig;
    if (!config) {
      throw new Error(`use() unable to get the workspace config`);
    }
    config.setExtension(aspectIdToAdd, {}, {
      overrideExisting: false,
      ignoreVersion: false
    });
    await config.write({
      reasonForChange: `use (${aspectIdStr})`
    });
    this.aspectLoader.addInMemoryConfiguredAspect(aspectIdToAdd);
    await this.runOnRootAspectAddedFunctions(aspectId, inWs);
    return aspectIdToAdd;
  }
  async getConfiguredUserAspectsPackages(options = {}) {
    const rawConfiguredAspects = this.workspace.getWorkspaceConfig()?.extensionsIds;
    const coreAspectsIds = this.aspectLoader.getCoreAspectIds();
    const userAspectsIds = (0, _lodash().difference)(rawConfiguredAspects, coreAspectsIds);
    const componentIdsToResolve = await this.workspace.resolveMultipleComponentIds(userAspectsIds.filter(id => !id.startsWith('file:')));
    const aspectsComponents = await this.importAndGetAspects(componentIdsToResolve);
    let componentsToGetPackages = aspectsComponents;
    if (options.externalsOnly) {
      const {
        nonWorkspaceComps
      } = await this.groupComponentsByWorkspaceExistence(aspectsComponents);
      componentsToGetPackages = nonWorkspaceComps;
    }
    const packages = componentsToGetPackages.map(aspectComponent => {
      const packageName = this.dependencyResolver.getPackageName(aspectComponent);
      const version = aspectComponent.id.version || '*';
      return {
        packageName,
        version
      };
    });
    return packages;
  }
  aspectDefsToRequireableComponents(aspectDefs) {
    const requireableComponents = aspectDefs.map(aspectDef => {
      const localPath = aspectDef.aspectPath;
      const component = aspectDef.component;
      if (!component) return undefined;
      const requireFunc = async () => {
        const plugins = this.aspectLoader.getPlugins(component, localPath);
        if (plugins.has()) {
          return plugins.load(_cli().MainRuntime.name);
        }
        const isModule = await this.aspectLoader.isEsmModule(localPath);
        const aspect = !isModule ?
        // eslint-disable-next-line global-require, import/no-dynamic-require
        require(localPath) :
        // : await this.aspectLoader.loadEsm(join(localPath, 'dist', 'index.js'));
        await this.aspectLoader.loadEsm(localPath);

        // require aspect runtimes
        const runtimePath = await this.aspectLoader.getRuntimePath(component, localPath, _cli().MainRuntime.name);
        if (runtimePath) {
          if (isModule) await this.aspectLoader.loadEsm(runtimePath);
          // eslint-disable-next-line global-require, import/no-dynamic-require
          require(runtimePath);
        }
        return aspect;
      };
      return new (_harmonyModules().RequireableComponent)(component, requireFunc);
    });
    return (0, _lodash().compact)(requireableComponents);
  }
  async linkIfMissingWorkspaceAspects(aspects) {
    const idsToLink = await Promise.all(aspects.map(async aspect => {
      if (!aspect.component) throw new Error(`linkIfMissingWorkspaceAspects, aspect.component is missing for ${aspect.aspectPath}`);
      const isInWs = await this.workspace.hasId(aspect.component.id);
      if (!isInWs) return null;
      const exist = await _fsExtra().default.pathExists(aspect.aspectPath);
      if (!exist) return aspect.component.id;
      return null;
    }));
    const idsToLinkWithoutNull = (0, _lodash().compact)(idsToLink);
    if (!idsToLinkWithoutNull.length) return;
    await (0, _workspaceModules().linkToNodeModulesByIds)(this.workspace, idsToLinkWithoutNull);
  }

  /**
   * This will return a resolver that knows to resolve aspects which are part of the workspace.
   * means aspects exist in the bitmap file
   * @param stringIds
   * @param runtimeName
   * @returns
   */
  getWorkspaceAspectResolver(stringIds, runtimeName) {
    const workspaceAspectResolver = async component => {
      const compStringId = component.id.toString();
      stringIds.push(compStringId);
      const localPath = await this.workspace.getComponentPackagePath(component);
      const runtimePath = runtimeName ? await this.aspectLoader.getRuntimePath(component, localPath, runtimeName) : null;
      const aspectFilePath = await this.aspectLoader.getAspectFilePath(component, localPath);
      this.logger.debug(`workspace resolveAspects, resolving id: ${compStringId}, localPath: ${localPath}, runtimePath: ${runtimePath}`);
      return {
        aspectPath: localPath,
        aspectFilePath,
        runtimePath
      };
    };
    return workspaceAspectResolver;
  }
  async runOnAspectsResolveFunctions(aspectsComponents) {
    const funcs = this.getOnAspectsResolveFunctions();
    await (0, _pMapSeries().default)(funcs, async func => {
      try {
        await func(aspectsComponents);
      } catch (err) {
        this.logger.error('failed running onAspectsResolve function', err);
      }
    });
  }
  getOnAspectsResolveFunctions() {
    const aspectsResolveFunctions = this.onAspectsResolveSlot.values();
    return aspectsResolveFunctions;
  }
  async runOnRootAspectAddedFunctions(aspectsId, inWs) {
    const funcs = this.getOnRootAspectAddedFunctions();
    await (0, _pMapSeries().default)(funcs, async func => {
      try {
        await func(aspectsId, inWs);
      } catch (err) {
        this.logger.error('failed running onRootAspectAdded function', err);
      }
    });
  }
  getOnRootAspectAddedFunctions() {
    const RootAspectAddedFunctions = this.onRootAspectAddedSlot.values();
    return RootAspectAddedFunctions;
  }

  /**
   * This will return a resolver that knows to resolve aspects which are not part of the workspace.
   * means aspects that does not exist in the bitmap file
   * instead it will resolve them from the node_modules recursively
   * @param graph
   * @param rootIds
   * @param runtimeName
   * @returns
   */
  getInstalledAspectResolver(graph, rootIds, runtimeName, opts = {
    throwOnError: false
  }) {
    const installedAspectsResolver = async component => {
      const compStringId = component.id.toString();
      // stringIds.push(compStringId);
      const localPath = await this.resolveInstalledAspectRecursively(component, rootIds, graph, opts);
      if (!localPath) return undefined;
      const runtimePath = runtimeName ? await this.aspectLoader.getRuntimePath(component, localPath, runtimeName) : null;
      const aspectFilePath = await this.aspectLoader.getAspectFilePath(component, localPath);
      this.logger.debug(`workspace resolveInstalledAspects, resolving id: ${compStringId}, localPath: ${localPath}, runtimePath: ${runtimePath}`);
      return {
        aspectPath: localPath,
        aspectFilePath,
        runtimePath
      };
    };
    return installedAspectsResolver;
  }
  async resolveInstalledAspectRecursively(aspectComponent, rootIds, graph, opts = {
    throwOnError: false
  }) {
    const aspectStringId = aspectComponent.id.toString();
    if (this.resolvedInstalledAspects.has(aspectStringId)) {
      const resolvedPath = this.resolvedInstalledAspects.get(aspectStringId);
      return resolvedPath;
    }
    if (rootIds.includes(aspectStringId)) {
      const localPath = await this.workspace.getComponentPackagePath(aspectComponent);
      this.resolvedInstalledAspects.set(aspectStringId, localPath);
      return localPath;
    }
    const parent = graph.predecessors(aspectStringId)[0];
    if (!parent) return undefined;
    const parentPath = await this.resolveInstalledAspectRecursively(parent.attr, rootIds, graph);
    if (!parentPath) {
      this.resolvedInstalledAspects.set(aspectStringId, null);
      return undefined;
    }
    const packageName = this.dependencyResolver.getPackageName(aspectComponent);
    try {
      const resolvedPath = (0, _toolboxModules().resolveFrom)(parentPath, [packageName]);
      const localPath = (0, _findRoot().default)(resolvedPath);
      this.resolvedInstalledAspects.set(aspectStringId, localPath);
      return localPath;
    } catch (error) {
      this.resolvedInstalledAspects.set(aspectStringId, null);
      if (opts.throwOnError) {
        throw error;
      }
      this.logger.consoleWarning(`failed resolving aspect ${aspectStringId} from ${parentPath}, error: ${error.message}`);
      return undefined;
    }
  }

  /**
   * Create a graph of aspects without the core aspects.
   * @param components
   * @param isAspect
   * @returns
   */
  async getAspectsGraphWithoutCore(components = [], isAspect) {
    const ids = components.map(component => component.id);
    const coreAspectsStringIds = this.aspectLoader.getCoreAspectIds();
    // TODO: @gilad it causes many issues we need to find a better solution. removed for now.
    // const coreAspectsComponentIds = coreAspectsStringIds.map((id) => ComponentID.fromString(id));
    // const aspectsIds = components.reduce((acc, curr) => {
    //   const currIds = curr.state.aspects.ids;
    //   acc = acc.concat(currIds);
    //   return acc;
    // }, [] as any);
    // const otherDependenciesMap = components.reduce((acc, curr) => {
    //   // const currIds = curr.state.dependencies.dependencies.map(dep => dep.id.toString());
    //   const currMap = curr.state.dependencies.getIdsMap();
    //   Object.assign(acc, currMap);
    //   return acc;
    // }, {});

    // const depsWhichAreNotAspects = difference(Object.keys(otherDependenciesMap), aspectsIds);
    // const depsWhichAreNotAspectsBitIds = depsWhichAreNotAspects.map((strId) => otherDependenciesMap[strId]);
    // We only want to load into the graph components which are aspects and not regular dependencies
    // This come to solve a circular loop when an env aspect use an aspect (as regular dep) and the aspect use the env aspect as its env
    return this.workspace.buildOneGraphForComponents(ids, coreAspectsStringIds, isAspect);
  }

  /**
   * Load all unloaded extensions from an extension list
   * this will resolve the extensions from the scope aspects capsules if they are not in the ws
   * Only use it for component extensions
   * for workspace/scope root aspect use the load aspects directly
   *
   * The reason we are loading component extensions with "scope aspects capsules" is because for component extensions
   * we might have the same extension in multiple versions
   * (for example I might have 2 components using different versions of the same env)
   * in such case, I can't install both version into the root of the node_modules so I need to place it somewhere else (capsules)
   * @param extensions list of extensions with config to load
   */
  async loadComponentsExtensions(extensions, originatedFrom, opts = {}) {
    const defaultOpts = {
      useScopeAspectsCapsule: true,
      throwOnError: false,
      hideMissingModuleError: !!this.workspace.inInstallContext,
      resolveEnvsFromRoots: this.resolveEnvsFromRoots
    };
    const mergedOpts = _objectSpread(_objectSpread({}, defaultOpts), opts);
    const extensionsIdsP = extensions.map(async extensionEntry => {
      // Core extension
      if (!extensionEntry.extensionId) {
        return extensionEntry.stringId;
      }
      const id = await this.workspace.resolveComponentId(extensionEntry.extensionId);
      // return this.resolveComponentId(extensionEntry.extensionId);
      return id.toString();
    });
    const extensionsIds = await Promise.all(extensionsIdsP);
    const harmonyExtensions = this.harmony.extensionsIds;
    const loadedExtensions = harmonyExtensions.filter(extId => {
      return this.harmony.extensions.get(extId)?.loaded;
    });
    const extensionsToLoad = (0, _lodash().difference)(extensionsIds, loadedExtensions);
    if (!extensionsToLoad.length) return;
    await this.loadAspects(extensionsToLoad, undefined, originatedFrom?.toString(), mergedOpts);
  }
  async isAspect(id) {
    const component = await this.workspace.get(id);
    const isUsingAspectEnv = this.envs.isUsingAspectEnv(component);
    const isUsingEnvEnv = this.envs.isUsingEnvEnv(component);
    const isValidAspect = isUsingAspectEnv || isUsingEnvEnv;
    return isValidAspect;
  }

  /**
   * same as `this.importAndGetMany()` with a specific error handling of ComponentNotFound
   */
  async importAndGetAspects(componentIds, throwOnError = true) {
    try {
      // We don't want to load the seeders as aspects as it will cause an infinite loop
      // once you try to load the seeder it will try to load the workspace component
      // that will arrive here again and again
      const loadOpts = {
        idsToNotLoadAsAspects: componentIds.map(id => id.toString())
      };
      return await this.workspace.importAndGetMany(componentIds, 'to load aspects from the workspace', loadOpts, throwOnError);
    } catch (err) {
      this.throwWsJsoncAspectNotFoundError(err);
      throw err;
    }
  }

  /**
   * split the provided components into 2 groups, one which are workspace components and the other which are not.
   * @param components
   * @returns
   */
  async groupComponentsByWorkspaceExistence(components, resolveEnvsFromRoots) {
    let workspaceComps = [];
    let nonWorkspaceComps = [];
    await Promise.all(components.map(async component => {
      const existOnWorkspace = await this.workspace.hasId(component.id);
      existOnWorkspace ? workspaceComps.push(component) : nonWorkspaceComps.push(component);
    }));
    if (resolveEnvsFromRoots) {
      const {
        rootComps,
        nonRootComps
      } = await this.groupComponentsByLoadFromRootComps(nonWorkspaceComps);
      workspaceComps = workspaceComps.concat(rootComps);
      nonWorkspaceComps = nonRootComps;
    }
    return {
      workspaceComps,
      nonWorkspaceComps
    };
  }
  async groupComponentsByLoadFromRootComps(components) {
    const rootComps = [];
    const nonRootComps = [];
    await Promise.all(components.map(async component => {
      const shouldLoadFromRootComps = await this.shouldLoadFromRootComps(component);
      if (shouldLoadFromRootComps) {
        rootComps.push(component);
        return;
      }
      nonRootComps.push(component);
    }));
    return {
      rootComps,
      nonRootComps
    };
  }
  async shouldLoadFromRootComps(component) {
    const rootDir = await this.workspace.getComponentPackagePath(component);
    const rootDirExist = await _fsExtra().default.pathExists(rootDir);
    const aspectFilePath = await this.aspectLoader.getAspectFilePath(component, rootDir);
    const aspectFilePathExist = aspectFilePath ? await _fsExtra().default.pathExists(aspectFilePath) : false;
    const pluginFiles = await this.aspectLoader.getPluginFiles(component, rootDir);

    // checking that we have the root dir (this means it's an aspect that needs to be loaded from there)
    // and validate that localPathExist so we can
    // really load the component from that path (if it's there it means that it's an env)
    if (rootDirExist && (aspectFilePathExist || pluginFiles.length)) {
      return true;
    }
    // If the component has env.jsonc we want to list it to be loaded from the root folder
    // even if it's not there yet
    // in that case we will fail to load it, and the user will need to run bit install
    if (this.envs.hasEnvManifest(component)) {
      return true;
    }
    return false;
  }

  /**
   * split the provided components into 2 groups, one which are workspace components and the other which are not.
   * @param components
   * @returns
   */
  async groupAspectDefsByWorkspaceExistence(aspectDefs) {
    const workspaceDefs = [];
    const nonWorkspaceDefs = [];
    await Promise.all(aspectDefs.map(async aspectDef => {
      const id = aspectDef.component?.id;
      const existOnWorkspace = id ? await this.workspace.hasId(id) : true;
      if (existOnWorkspace) {
        workspaceDefs.push(aspectDef);
        return;
      }
      const shouldLoadFromRootComps = aspectDef.component ? await this.shouldLoadFromRootComps(aspectDef.component) : undefined;
      if (shouldLoadFromRootComps) {
        workspaceDefs.push(aspectDef);
        return;
      }
      nonWorkspaceDefs.push(aspectDef);
    }));
    return {
      workspaceDefs,
      nonWorkspaceDefs
    };
  }
  async groupIdsByWorkspaceExistence(ids, resolveEnvsFromRoots) {
    let workspaceIds = [];
    let nonWorkspaceIds = [];
    await Promise.all(ids.map(async id => {
      const existOnWorkspace = await this.workspace.hasId(id);
      existOnWorkspace ? workspaceIds.push(id) : nonWorkspaceIds.push(id);
    }));
    // We need to bring the components in order to really group them with taking the root comps into account
    const scopeComponents = await this.importAndGetAspects(nonWorkspaceIds);
    const {
      nonWorkspaceComps,
      workspaceComps
    } = await this.groupComponentsByWorkspaceExistence(scopeComponents, resolveEnvsFromRoots);
    workspaceIds = workspaceIds.concat(workspaceComps.map(c => c.id));
    nonWorkspaceIds = nonWorkspaceComps.map(c => c.id);
    return {
      workspaceIds,
      nonWorkspaceIds
    };
  }
}
exports.WorkspaceAspectsLoader = WorkspaceAspectsLoader;

//# sourceMappingURL=workspace-aspects-loader.js.map