"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ScopeAspectsLoader = void 0;
function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));
  _pMapSeries = function () {
    return data;
  };
  return data;
}
function _fsExtra() {
  const data = require("fs-extra");
  _fsExtra = function () {
    return data;
  };
  return data;
}
function _path() {
  const data = require("path");
  _path = function () {
    return data;
  };
  return data;
}
function _constants() {
  const data = require("@teambit/legacy/dist/constants");
  _constants = function () {
    return data;
  };
  return data;
}
function _lodash() {
  const data = require("lodash");
  _lodash = function () {
    return data;
  };
  return data;
}
function _cli() {
  const data = require("@teambit/cli");
  _cli = function () {
    return data;
  };
  return data;
}
function _harmonyModules() {
  const data = require("@teambit/harmony.modules.requireable-component");
  _harmonyModules = 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 ScopeAspectsLoader {
  constructor(scope, aspectLoader, envs, isolator, logger, globalConfig) {
    this.scope = scope;
    this.aspectLoader = aspectLoader;
    this.envs = envs;
    this.isolator = isolator;
    this.logger = logger;
    this.globalConfig = globalConfig;
  }
  async loadAspects(ids, throwOnError = false, neededFor, lane, opts) {
    if (!ids.length) return [];
    // 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.info(`${loggerPrefix} loading ${ids.length} aspects.
ids: ${ids.join(', ')}
needed-for: ${neededFor || '<unknown>'}`);
    const grouped = await this.groupAspectIdsByEnvOfTheList(ids, lane);
    this.logger.info(`${loggerPrefix} getManifestsAndLoadAspects for grouped.envs, total ${grouped.envs?.length || 0}`);
    const envsManifestsIds = await this.getManifestsAndLoadAspects(grouped.envs, throwOnError, lane, opts);
    this.logger.info(`${loggerPrefix} getManifestsAndLoadAspects for grouped.other, total ${grouped.other?.length || 0}`);
    const otherManifestsIds = await this.getManifestsAndLoadAspects(grouped.other, throwOnError, lane, opts);
    this.logger.debug(`${loggerPrefix} finish loading aspects`);
    return envsManifestsIds.concat(otherManifestsIds);
  }

  /**
   * This function get's a list of aspect ids and return them grouped by whether any of them is the env of other from the list
   * @param ids
   */
  async groupAspectIdsByEnvOfTheList(ids, lane) {
    const components = await this.getNonLoadedAspects(ids, lane);
    const envsIds = (0, _lodash().uniq)(components.map(component => this.envs.getEnvId(component)).filter(envId => !this.aspectLoader.isCoreEnv(envId)));
    const grouped = (0, _lodash().groupBy)(ids, id => {
      if (envsIds.includes(id)) return 'envs';
      return 'other';
    });
    return grouped;
  }
  async getManifestsAndLoadAspects(ids = [], throwOnError = false, lane, opts) {
    const {
      manifests: scopeManifests,
      potentialPluginsIds
    } = await this.getManifestsGraphRecursively(ids, [], throwOnError, lane, opts);
    await this.aspectLoader.loadExtensionsByManifests(scopeManifests);
    const {
      manifests: scopePluginsManifests
    } = await this.getManifestsGraphRecursively(potentialPluginsIds, [], throwOnError, lane, opts);
    await this.aspectLoader.loadExtensionsByManifests(scopePluginsManifests);
    const allManifests = scopeManifests.concat(scopePluginsManifests);
    return (0, _lodash().compact)(allManifests.map(manifest => manifest.id));
  }
  async getManifestsGraphRecursively(ids, visited = [], throwOnError = false, lane, opts = {}) {
    ids = (0, _lodash().uniq)(ids);
    const optsWithDefaults = _objectSpread({
      loadCustomEnvs: false
    }, opts);
    this.logger.debug(`getManifestsGraphRecursively, ids:\n${ids.join('\n')}`);
    const nonVisitedId = ids.filter(id => !visited.includes(id));
    if (!nonVisitedId.length) {
      return {
        manifests: [],
        potentialPluginsIds: []
      };
    }
    const components = await this.getNonLoadedAspects(nonVisitedId, lane);
    // This is usually not required, right now it required when signing aspects with custom envs.
    // if you see another case where it's required, please consult @Gilad before passing it as true
    // as it might have performance implications (like creating unnecessary capsules for aspects that are not needed
    // during bit create for example, like loading the env of the env (aka bitdev.general/envs/bit-env) when
    // loading the component's env.)
    if (optsWithDefaults.loadCustomEnvs) {
      // Adding all the envs ids to the array to support case when one (or more) of the aspects has custom aspect env
      const customEnvsIds = components.map(component => this.envs.getEnvId(component)).filter(envId => !this.aspectLoader.isCoreEnv(envId));

      // In case there is custom env we need to load it right away, otherwise we will fail during the require aspects
      await this.getManifestsAndLoadAspects(customEnvsIds, undefined, lane);
    }
    visited.push(...nonVisitedId);
    const manifests = await this.requireAspects(components, throwOnError, optsWithDefaults);
    const potentialPluginsIds = (0, _lodash().compact)(manifests.map((manifest, index) => {
      if (this.aspectLoader.isValidAspect(manifest)) return undefined;
      // return index;
      return components[index].id.toString();
    }));
    const depsToLoad = [];
    await (0, _pMapSeries().default)(manifests, async manifest => {
      depsToLoad.push(...(manifest.dependencies || []));
      // @ts-ignore
      (manifest._runtimes || []).forEach(runtime => {
        depsToLoad.push(...(runtime.dependencies || []));
      });
      const depIds = depsToLoad.map(d => d.id).filter(id => id);
      this.logger.debug(`getManifestsGraphRecursively, id: ${manifest.id || '<unknown>'}, found ${depIds.length}: ${depIds.join(', ')}`);
      const {
        manifests: loaded
      } = await this.getManifestsGraphRecursively(depIds, visited, throwOnError, lane, opts);
      manifests.push(...loaded);
    });
    return {
      manifests,
      potentialPluginsIds
    };
  }
  async getNonLoadedAspects(ids, lane) {
    const notLoadedIds = ids.filter(id => !this.aspectLoader.isAspectLoaded(id));
    if (!notLoadedIds.length) return [];
    const coreAspectsStringIds = this.aspectLoader.getCoreAspectIds();
    const idsWithoutCore = (0, _lodash().difference)(ids, coreAspectsStringIds);
    const aspectIds = idsWithoutCore.filter(id => !id.startsWith('file://'));
    // TODO: use diff instead of filter twice
    const localAspects = ids.filter(id => id.startsWith('file://'));
    this.scope.localAspects = (0, _lodash().uniq)(this.scope.localAspects.concat(localAspects));
    // load local aspects for debugging purposes.
    await this.aspectLoader.loadAspectFromPath(localAspects);
    const componentIds = await this.scope.resolveMultipleComponentIds(aspectIds);
    if (!componentIds || !componentIds.length) return [];
    await this.scope.import(componentIds, {
      reFetchUnBuiltVersion: false,
      lane,
      reason: 'for loading aspects from the scope'
    });
    const components = await this.scope.getMany(componentIds);
    return components;
  }
  async getResolvedAspects(components, opts) {
    if (!components || !components.length) return [];
    const isolateOpts = this.getIsolateOpts(opts);
    const network = await this.isolator.isolateComponents(components.map(c => c.id),
    // includeFromNestedHosts - to support case when you are in a workspace, trying to load aspect defined in the workspace.jsonc but not part of the workspace
    isolateOpts, this.scope.legacyScope);
    const capsules = network.seedersCapsules;
    return capsules.map(capsule => {
      return new (_harmonyModules().RequireableComponent)(capsule.component, async () => {
        // eslint-disable-next-line global-require, import/no-dynamic-require
        const plugins = this.aspectLoader.getPlugins(capsule.component, capsule.path);
        if (plugins.has()) {
          await this.compileIfNoDist(capsule, capsule.component);
          return plugins.load(_cli().MainRuntime.name);
        }
        // eslint-disable-next-line global-require, import/no-dynamic-require
        const aspect = require(capsule.path);
        const scopeRuntime = await this.aspectLoader.getRuntimePath(capsule.component, capsule.path, 'scope');
        const mainRuntime = await this.aspectLoader.getRuntimePath(capsule.component, capsule.path, _cli().MainRuntime.name);
        const runtimePath = scopeRuntime || mainRuntime;
        // eslint-disable-next-line global-require, import/no-dynamic-require
        if (runtimePath) require(runtimePath);
        // eslint-disable-next-line global-require, import/no-dynamic-require
        return aspect;
      }, capsule);
    });
  }
  async compileIfNoDist(capsule, component) {
    let compiler;
    try {
      const env = this.envs.getEnv(component);
      compiler = env.env.getCompiler();
    } catch (err) {
      this.logger.info(`compileIfNoDist: failed loading compiler for ${component.id.toString()} in capsule ${capsule.path}, error: ${err.message}`);
    }
    const distDir = compiler?.distDir || _constants().DEFAULT_DIST_DIRNAME;
    const distExists = (0, _fsExtra().existsSync)((0, _path().join)(capsule.path, distDir));
    if (distExists) return;
    if (!compiler) {
      throw new (_bitError().BitError)(`unable to compile aspect/env ${component.id.toString()}, no compiler found`);
    }
    const compiledCode = (await Promise.all(component.filesystem.files.flatMap(async file => {
      // @ts-ignore - we know it's not null, we have throw error above if yes
      if (!compiler.isFileSupported(file.path)) {
        return [{
          outputText: file.contents.toString('utf8'),
          outputPath: file.path
        }];
      }
      // @ts-ignore - we know it's not null, we have throw error above if yes
      if (compiler.transpileFile) {
        // @ts-ignore - we know it's not null, we have throw error above if yes
        return compiler.transpileFile(file.contents.toString('utf8'), {
          filePath: file.path,
          componentDir: capsule.path
        });
      }
      return [];
    }))).flat();
    await Promise.all((0, _lodash().compact)(compiledCode).map(compiledFile => {
      // @ts-ignore - we know it's not null, we have throw error above if yes
      const path = compiler.getDistPathBySrcPath(compiledFile.outputPath);
      return capsule?.outputFile(path, compiledFile.outputText);
    }));
  }
  async tryCompile(requirableAspect) {
    if (requirableAspect.capsule) return this.compileIfNoDist(requirableAspect.capsule, requirableAspect.component);
    return undefined;
  }
  async requireAspects(components, throwOnError = false, opts = {}) {
    const requireableExtensions = await this.getResolvedAspects(components, opts);
    if (!requireableExtensions) {
      return [];
    }
    let error;
    let erroredId = '';
    const requireWithCatch = async requireableAspects => {
      error = undefined;
      try {
        const manifests = await (0, _pMapSeries().default)(requireableAspects, async requireableExtension => {
          try {
            return await this.aspectLoader.doRequire(requireableExtension);
          } catch (err) {
            erroredId = requireableExtension.component.id.toString();
            if (err.code === 'MODULE_NOT_FOUND') {
              try {
                await this.tryCompile(requireableExtension);
                return await this.aspectLoader.doRequire(requireableExtension);
              } catch (newErr) {
                error = newErr;
                throw newErr;
              }
            }
            error = err;
            throw err;
          }
        });
        return manifests;
      } catch (err) {
        return null;
      }
    };
    const manifests = await requireWithCatch(requireableExtensions);
    if (!error) {
      return (0, _lodash().compact)(manifests);
    }
    if (error.code === 'MODULE_NOT_FOUND') {
      this.logger.warn(`failed loading aspects from capsules due to MODULE_NOT_FOUND error, re-creating the capsules and trying again`);
      const resolvedAspectsAgain = await this.getResolvedAspects(components, _objectSpread(_objectSpread({}, opts), {}, {
        skipIfExists: false
      }));
      const manifestAgain = await requireWithCatch(resolvedAspectsAgain);
      if (!error) {
        return (0, _lodash().compact)(manifestAgain);
      }
    }
    this.aspectLoader.handleExtensionLoadingError(error, erroredId, throwOnError);
    return [];
  }
  shouldUseDatedCapsules() {
    const globalConfig = this.globalConfig.getSync(_constants().CFG_USE_DATED_CAPSULES);
    // @ts-ignore
    return globalConfig === true || globalConfig === 'true';
  }
  shouldCacheLockFileOnly() {
    const globalConfig = this.globalConfig.getSync(_constants().CFG_CACHE_LOCK_ONLY_CAPSULES);
    // @ts-ignore
    return globalConfig === true || globalConfig === 'true';
  }
  getAspectCapsulePath() {
    const defaultPath = `${this.scope.path}-aspects`;
    if (this.scope.isGlobalScope) {
      return this.globalConfig.getSync(_constants().CFG_CAPSULES_GLOBAL_SCOPE_ASPECTS_BASE_DIR) || defaultPath;
    }
    return this.globalConfig.getSync(_constants().CFG_CAPSULES_SCOPES_ASPECTS_BASE_DIR) || defaultPath;
  }
  shouldUseHashForCapsules() {
    if (this.scope.isGlobalScope) {
      return !this.globalConfig.getSync(_constants().CFG_CAPSULES_GLOBAL_SCOPE_ASPECTS_BASE_DIR);
    }
    return !this.globalConfig.getSync(_constants().CFG_CAPSULES_SCOPES_ASPECTS_BASE_DIR);
  }
  getAspectsPackageManager() {
    return this.scope.aspectsPackageManager;
  }
  getAspectsNodeLinker() {
    return this.scope.aspectsNodeLinker;
  }
  async resolveUserAspects(runtimeName, userAspectsIds, opts) {
    if (!userAspectsIds || !userAspectsIds.length) return [];
    const components = await this.scope.getMany(userAspectsIds);
    const isolateOpts = this.getIsolateOpts(opts);
    const network = await this.isolator.isolateComponents(userAspectsIds, isolateOpts, this.scope.legacyScope);
    const capsules = network.seedersCapsules;
    const aspectDefs = await this.aspectLoader.resolveAspects(components, async component => {
      const capsule = capsules.getCapsule(component.id);
      if (!capsule) throw new Error(`failed loading aspect: ${component.id.toString()}`);
      const localPath = capsule.path;
      const runtimePath = runtimeName ? await this.aspectLoader.getRuntimePath(component, localPath, runtimeName) : null;
      const aspectFilePath = await this.aspectLoader.getAspectFilePath(component, localPath);
      this.logger.debug(`scope resolveUserAspects, resolving id: ${component.id.toString()}, localPath: ${localPath}, runtimePath: ${runtimePath}`);
      return {
        id: capsule.component.id,
        aspectPath: localPath,
        aspectFilePath,
        runtimePath
      };
    });
    return aspectDefs;
  }
  async resolveAspects(runtimeName, componentIds, opts) {
    const originalStringIds = componentIds?.map(id => id.toString());
    this.logger.debug(`scope resolveAspects, runtimeName: ${runtimeName}, componentIds: ${originalStringIds}`);
    const defaultOpts = {
      excludeCore: false,
      requestedOnly: false,
      filterByRuntime: true
    };
    const mergedOpts = _objectSpread(_objectSpread({}, defaultOpts), opts);
    const coreAspectsIds = this.aspectLoader.getCoreAspectIds();
    let userAspectsIds;
    // let requestedCoreStringIds;
    if (componentIds && componentIds.length) {
      const groupedByIsCore = (0, _lodash().groupBy)(componentIds, id => coreAspectsIds.includes(id.toString()));
      userAspectsIds = groupedByIsCore.false || [];
      // requestedCoreStringIds = groupedByIsCore.true?.map((id) => id.toStringWithoutVersion()) || [];
    } else {
      userAspectsIds = await this.scope.resolveMultipleComponentIds(this.aspectLoader.getUserAspects());
    }
    const localResolved = await this.aspectLoader.resolveLocalAspects(this.scope.localAspects, runtimeName);
    const withoutLocalAspects = userAspectsIds.filter(aspectId => {
      return !localResolved.find(localAspect => {
        return localAspect.id === aspectId.toStringWithoutVersion();
      });
    });
    const userAspectsDefs = await this.resolveUserAspects(runtimeName, withoutLocalAspects, opts);
    const coreAspectsDefs = await this.aspectLoader.getCoreAspectDefs(runtimeName);
    const allDefs = userAspectsDefs.concat(coreAspectsDefs).concat(localResolved);
    // const userAspectsIdsWithoutVersion = userAspectsIds.map((aspectId) => aspectId.toStringWithoutVersion());
    // const userAspectsIdsWithoutVersionAndCoreRequested = userAspectsIdsWithoutVersion.concat(requestedCoreStringIds);
    const filteredDefs = this.aspectLoader.filterAspectDefs(allDefs, componentIds || userAspectsIds, runtimeName, mergedOpts);
    return filteredDefs;
  }
  getIsolateOpts(opts) {
    const overrideOpts = {
      skipIfExists: opts?.skipIfExists ?? true,
      installOptions: {
        packageManagerConfigRootDir: opts?.packageManagerConfigRootDir
      },
      context: {
        workspaceName: opts?.workspaceName
      }
    };
    const isolateOpts = (0, _lodash().defaultsDeep)(overrideOpts, this.getDefaultIsolateOpts());
    return isolateOpts;
  }
  getDefaultIsolateOpts() {
    const useHash = this.shouldUseHashForCapsules();
    const useDatedDirs = this.shouldUseDatedCapsules();
    const cacheLockFileOnly = this.shouldCacheLockFileOnly();
    const nodeLinker = this.getAspectsNodeLinker();
    const opts = {
      datedDirId: this.scope.name,
      baseDir: this.getAspectCapsulePath(),
      useHash,
      packageManager: this.getAspectsPackageManager(),
      nodeLinker,
      useDatedDirs,
      cacheLockFileOnly,
      skipIfExists: true,
      seedersOnly: true,
      includeFromNestedHosts: true,
      host: this.scope,
      installOptions: {
        copyPeerToRuntimeOnRoot: true,
        useNesting: true,
        copyPeerToRuntimeOnComponents: true,
        installPeersFromEnvs: true
      },
      context: {
        aspects: true
      }
    };
    return opts;
  }
}
exports.ScopeAspectsLoader = ScopeAspectsLoader;

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