"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ScopeComponentLoader = void 0;
function _component() {
  const data = require("@teambit/component");
  _component = function () {
    return data;
  };
  return data;
}
function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));
  _pMapSeries = function () {
    return data;
  };
  return data;
}
function _semver() {
  const data = require("semver");
  _semver = function () {
    return data;
  };
  return data;
}
function _objects() {
  const data = require("@teambit/legacy/dist/scope/objects");
  _objects = function () {
    return data;
  };
  return data;
}
function _modelComponent() {
  const data = require("@teambit/legacy/dist/scope/models/model-component");
  _modelComponent = function () {
    return data;
  };
  return data;
}
function _harmonyModules() {
  const data = require("@teambit/harmony.modules.in-memory-cache");
  _harmonyModules = 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 ScopeComponentLoader {
  constructor(scope, logger) {
    this.scope = scope;
    this.logger = logger;
    _defineProperty(this, "componentsCache", void 0);
    // cache loaded components
    _defineProperty(this, "importedComponentsCache", void 0);
    this.componentsCache = (0, _harmonyModules().createInMemoryCache)({
      maxSize: (0, _harmonyModules().getMaxSizeForComponents)()
    });
    this.importedComponentsCache = (0, _harmonyModules().createInMemoryCache)({
      maxAge: 1000 * 60 * 30
    }); // 30 min
  }
  async get(id, importIfMissing = true, useCache = true) {
    const fromCache = this.getFromCache(id);
    if (fromCache && useCache) {
      return fromCache;
    }
    const idStr = id.toString();
    this.logger.debug(`ScopeComponentLoader.get, loading ${idStr}`);
    const legacyId = id;
    let modelComponent = await this.scope.legacyScope.getModelComponentIfExist(id);
    // import if missing
    if (!modelComponent && importIfMissing && this.scope.isExported(id) && !this.importedComponentsCache.get(id.toString())) {
      await this.scope.import([id], {
        reason: `${id.toString()} because it's missing from the local scope`
      });
      this.importedComponentsCache.set(id.toString(), true);
      modelComponent = await this.scope.legacyScope.getModelComponentIfExist(id);
    }
    // Search with scope name for bare scopes
    if (!modelComponent && !legacyId.scope) {
      id = id.changeScope(this.scope.name);
      modelComponent = await this.scope.legacyScope.getModelComponentIfExist(id);
    }
    if (!modelComponent) return undefined;
    const versionStr = id.hasVersion() ? id.version : modelComponent.getHeadRegardlessOfLaneAsTagOrHash(true);
    if (versionStr === _modelComponent().VERSION_ZERO) return undefined;
    const newId = id.changeVersion(versionStr);
    const version = await modelComponent.loadVersion(versionStr, this.scope.legacyScope.objects);
    const snap = await this.getHeadSnap(modelComponent);
    const state = await this.createStateFromVersion(id, version);
    const tagMap = this.getTagMap(modelComponent);
    const component = new (_component().Component)(newId, snap, state, tagMap, this.scope);
    this.componentsCache.set(idStr, component);
    return component;
  }
  async getFromConsumerComponent(consumerComponent) {
    const id = consumerComponent.id;
    const modelComponent = await this.scope.legacyScope.getModelComponent(id);
    // :TODO move to head snap once we have it merged, for now using `latest`.
    const version = consumerComponent.pendingVersion || (await modelComponent.loadVersion(id.version, this.scope.legacyScope.objects));
    const snap = await this.getHeadSnap(modelComponent);
    const state = await this.createStateFromVersion(id, version, consumerComponent);
    const tagMap = this.getTagMap(modelComponent);
    return new (_component().Component)(id, snap, state, tagMap, this.scope);
  }

  /**
   * get a component from a remote without importing it
   */
  async getRemoteComponent(id) {
    const compImport = this.scope.legacyScope.scopeImporter;
    const objectList = await compImport.getRemoteComponent(id);
    // it's crucial to add all objects to the Repository cache. otherwise, later, when it asks
    // for the consumerComponent from the legacyScope, it won't work.
    objectList?.getAll().forEach(obj => this.scope.legacyScope.objects.setCache(obj));
    const consumerComponent = await this.scope.legacyScope.getConsumerComponent(id);
    return this.getFromConsumerComponent(consumerComponent);
  }

  /**
   * get components from a remote without importing it
   */
  async getManyRemoteComponents(ids) {
    const compImport = this.scope.legacyScope.scopeImporter;
    const legacyIds = ids.map(id => id);
    const objectList = await compImport.getManyRemoteComponents(legacyIds);
    // it's crucial to add all objects to the Repository cache. otherwise, later, when it asks
    // for the consumerComponent from the legacyScope, it won't work.
    objectList?.getAll().forEach(obj => this.scope.legacyScope.objects.setCache(obj));
    return (0, _pMapSeries().default)(legacyIds, async legacyId => {
      const consumerComponent = await this.scope.legacyScope.getConsumerComponent(legacyId);
      return this.getFromConsumerComponent(consumerComponent);
    });
  }
  async getState(id, hash) {
    const version = await this.scope.legacyScope.objects.load(new (_objects().Ref)(hash));
    return this.createStateFromVersion(id, version);
  }
  async getSnap(id, hash) {
    const getVersionObject = async () => {
      try {
        const snap = await this.scope.legacyScope.objects.load(new (_objects().Ref)(hash), true);
        return snap;
      } catch (err) {
        if (err.code === 'ENOENT') {
          const errMsg = `fatal: snap "${hash}" file for component "${id.toString()}" was not found in the filesystem`;
          this.logger.error(errMsg, err);
          throw new Error(errMsg);
        } else {
          throw err;
        }
      }
    };
    const version = await getVersionObject();
    return this.createSnapFromVersion(version);
  }
  clearCache() {
    this.componentsCache.deleteAll();
  }

  /**
   * make sure that not only the id-str match, but also the legacy-id.
   * this is needed because the ComponentID.toString() is the same whether or not the legacy-id has
   * scope-name, as it includes the defaultScope if the scope is empty.
   * as a result, when out-of-sync is happening and the id is changed to include scope-name in the
   * legacy-id, the component is the cache has the old id.
   */
  getFromCache(id) {
    const idStr = id.toString();
    const fromCache = this.componentsCache.get(idStr);
    if (fromCache && fromCache.id.isEqual(id)) {
      return fromCache;
    }
    return undefined;
  }
  getTagMap(modelComponent) {
    const tagMap = new (_component().TagMap)();
    const allVersions = modelComponent.versionsIncludeOrphaned;
    Object.keys(allVersions).forEach(versionStr => {
      const tag = new (_component().Tag)(allVersions[versionStr].toString(), new (_semver().SemVer)(versionStr));
      tagMap.set(tag.version, tag);
    });
    return tagMap;
  }
  async getHeadSnap(modelComponent) {
    const head = modelComponent.getHeadRegardlessOfLane();
    if (!head) {
      // happens for example when on main and merging a lane.
      return null;
    }
    const version = await modelComponent.loadVersion(head.toString(), this.scope.legacyScope.objects, false);
    if (!version) {
      // might happen when the component is just a dependency and a previous version was needed.
      return null;
    }
    return this.createSnapFromVersion(version);
  }
  createSnapFromVersion(version) {
    return new (_component().Snap)(version.hash().toString(), new Date(parseInt(version.log.date)), version.parents.map(p => p.toString()), {
      displayName: version.log.username || 'unknown',
      email: version.log.email || 'unknown@anywhere'
    }, version.log.message);
  }
  async createStateFromVersion(id, version, consumerComponent) {
    consumerComponent = consumerComponent || (await this.scope.legacyScope.getConsumerComponent(id));
    const state = new (_component().State)(
    // We use here the consumerComponent.extensions instead of version.extensions
    // because as part of the conversion to consumer component the artifacts are initialized as Artifact instances
    new (_component().Config)(consumerComponent),
    // todo: see the comment of this "createAspectListFromLegacy" method. the aspect ids may be incorrect.
    // find a better way to get the ids correctly.
    this.scope.componentExtension.createAspectListFromLegacy(consumerComponent.extensions), _component().ComponentFS.fromVinyls(consumerComponent.files), version.dependencies, consumerComponent);
    return state;
  }
}
exports.ScopeComponentLoader = ScopeComponentLoader;

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