"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

function _ramda() {
  const data = _interopRequireDefault(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));

  _pMapSeries = function () {
    return data;
  };

  return data;
}

function _remotes() {
  const data = require("../../remotes");

  _remotes = function () {
    return data;
  };

  return data;
}

function _enrichContextFromGlobal() {
  const data = _interopRequireDefault(require("../../hooks/utils/enrich-context-from-global"));

  _enrichContextFromGlobal = function () {
    return data;
  };

  return data;
}

function _bitId() {
  const data = require("../../bit-id");

  _bitId = function () {
    return data;
  };

  return data;
}

function _versionDependencies() {
  const data = _interopRequireDefault(require("../version-dependencies"));

  _versionDependencies = function () {
    return data;
  };

  return data;
}

function _logger() {
  const data = _interopRequireDefault(require("../../logger/logger"));

  _logger = function () {
    return data;
  };

  return data;
}

function _exceptions() {
  const data = require("../network/exceptions");

  _exceptions = function () {
    return data;
  };

  return data;
}

function _exceptions2() {
  const data = require("../exceptions");

  _exceptions2 = function () {
    return data;
  };

  return data;
}

function _analytics() {
  const data = require("../../analytics/analytics");

  _analytics = function () {
    return data;
  };

  return data;
}

function _sources() {
  const data = _interopRequireDefault(require("../repositories/sources"));

  _sources = function () {
    return data;
  };

  return data;
}

function _generalError() {
  const data = _interopRequireDefault(require("../../error/general-error"));

  _generalError = function () {
    return data;
  };

  return data;
}

function _scopeRemotes() {
  const data = require("../scope-remotes");

  _scopeRemotes = function () {
    return data;
  };

  return data;
}

function _utils() {
  const data = require("../../utils");

  _utils = function () {
    return data;
  };

  return data;
}

const removeNils = _ramda().default.reject(_ramda().default.isNil);

class ScopeComponentsImporter {
  constructor(scope) {
    if (!scope) throw new Error('unable to instantiate ScopeComponentsImporter without Scope');
    this.scope = scope;
    this.sources = scope.sources;
  }

  static getInstance(scope) {
    return new ScopeComponentsImporter(scope);
  }
  /**
   * 1. Local objects, fetch from local. (done by this.sources.getMany method)
   * 2. Fetch flattened dependencies (done by toVersionDependencies method). If they're not locally, fetch from remote
   * and save them locally.
   * 3. External objects, fetch from a remote and save locally. (done by this.getExternalOnes method).
   */


  importMany(ids, cache = true, persist = true) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debugAndAddBreadCrumb('ScopeComponentsImporter', 'importMany: {ids}', {
        ids: ids.toString()
      });

      const idsWithoutNils = removeNils(ids);
      if (_ramda().default.isEmpty(idsWithoutNils)) return Promise.resolve([]);

      const [externals, locals] = _ramda().default.splitWhen(id => id.isLocal(_this.scope.name), idsWithoutNils);

      const localDefs = yield _this.sources.getMany(locals);
      const versionDeps = yield (0, _pMapSeries().default)(localDefs, def => {
        if (!def.component) throw new (_exceptions2().ComponentNotFound)(def.id.toString());
        return _this.componentToVersionDependencies(def.component, def.id);
      });

      _logger().default.debugAndAddBreadCrumb('ScopeComponentsImporter', 'importMany: successfully fetched local components and their dependencies. Going to fetch externals');

      const remotes = yield (0, _scopeRemotes().getScopeRemotes)(_this.scope);
      const externalDeps = yield _this._getExternalMany(externals, remotes, cache, persist);
      return versionDeps.concat(externalDeps);
    })();
  }

  importManyWithoutDependencies(ids, cache = true) {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!ids.length) return [];

      _logger().default.debug(`scope.importManyOnes. Ids: ${ids.join(', ')}, cache: ${cache.toString()}`);

      _analytics().Analytics.addBreadCrumb('importManyOnes', `scope.importManyOnes. Ids: ${_analytics().Analytics.hashData(ids)}`);

      const idsWithoutNils = removeNils(ids);
      if (_ramda().default.isEmpty(idsWithoutNils)) return Promise.resolve([]);
      const [externals, locals] = (0, _utils().splitBy)(idsWithoutNils, id => id.isLocal(_this2.scope.name));
      const localDefs = yield _this2.sources.getMany(locals);
      const componentVersionArr = yield Promise.all(localDefs.map(def => {
        if (!def.component) throw new (_exceptions2().ComponentNotFound)(def.id.toString()); // $FlowFixMe it must have a version

        return def.component.toComponentVersion(def.id.version);
      }));
      const remotes = yield (0, _scopeRemotes().getScopeRemotes)(_this2.scope);
      const externalDeps = yield _this2._getExternalManyWithoutDependencies(externals, remotes, cache);
      return componentVersionArr.concat(externalDeps);
    })();
  }
  /**
   * todo: improve performance by finding all versions needed and fetching them in one request from the server
   * currently it goes to the server twice. First, it asks for the last version of each id, and then it goes again to
   * ask for the older versions.
   */


  importManyWithAllVersions(ids, cache = true) {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug(`scope.getManyWithAllVersions, Ids: ${ids.join(', ')}`);

      _analytics().Analytics.addBreadCrumb('getManyWithAllVersions', `scope.getManyWithAllVersions, Ids: ${_analytics().Analytics.hashData(ids)}`);

      const idsWithoutNils = removeNils(ids);
      if (_ramda().default.isEmpty(idsWithoutNils)) return Promise.resolve([]);
      const versionDependenciesArr = yield _this3.importMany(idsWithoutNils, cache);
      const allIdsWithAllVersions = new (_bitId().BitIds)();
      versionDependenciesArr.forEach(versionDependencies => {
        const versions = versionDependencies.component.component.listVersions();
        const idsWithAllVersions = versions.map(version => {
          if (version === versionDependencies.component.version) return null; // imported already

          const versionId = versionDependencies.component.id;
          return versionId.changeVersion(version);
        });
        allIdsWithAllVersions.push(...removeNils(idsWithAllVersions));
      });
      yield _this3.importManyWithoutDependencies(allIdsWithAllVersions);
      return versionDependenciesArr;
    })();
  }

  importDependencies(dependencies) {
    return new Promise((resolve, reject) => {
      return this.importMany(dependencies).then(resolve).catch(e => {
        _logger().default.error(`importDependencies got an error: ${JSON.stringify(e)}`);

        if (e instanceof _exceptions().RemoteScopeNotFound || e instanceof _exceptions().PermissionDenied) return reject(e);
        return reject(new (_exceptions2().DependencyNotFound)(e.id));
      });
    });
  }

  componentToVersionDependencies(component, id) {
    var _this4 = this;

    return (0, _bluebird().coroutine)(function* () {
      // $FlowFixMe
      const versionComp = component.toComponentVersion(id.version); // $FlowFixMe

      const source = id.scope;
      const version = yield versionComp.getVersion(_this4.scope.objects);

      if (!version) {
        _logger().default.debug(`toVersionDependencies, component ${component.id().toString()}, version ${versionComp.version} not found, going to fetch from a remote`);

        if (component.scope === _this4.scope.name) {
          // it should have been fetched locally, since it wasn't found, this is an error
          throw new (_generalError().default)(`Version ${versionComp.version} of ${component.id().toString()} was not found in scope ${_this4.scope.name}`);
        }

        return (0, _scopeRemotes().getScopeRemotes)(_this4.scope).then(remotes => {
          return _this4._getExternal({
            id,
            remotes,
            localFetch: false
          });
        });
      }

      _logger().default.debug(`toVersionDependencies, component ${component.id().toString()}, version ${versionComp.version} found, going to collect its dependencies`);

      const dependencies = yield _this4.importManyWithoutDependencies(version.flattenedDependencies);
      const devDependencies = yield _this4.importManyWithoutDependencies(version.flattenedDevDependencies);
      const compilerDependencies = yield _this4.importManyWithoutDependencies(version.flattenedCompilerDependencies);
      const testerDependencies = yield _this4.importManyWithoutDependencies(version.flattenedTesterDependencies);
      return new (_versionDependencies().default)(versionComp, dependencies, devDependencies, compilerDependencies, testerDependencies, source);
    })();
  }

  componentsToComponentsObjects(components, clientVersion) {
    return (0, _pMapSeries().default)(components, component => component.toObjects(this.scope.objects, clientVersion));
  }
  /**
   * get ConsumerComponent by bitId. if the component was not found locally, import it from a remote scope
   */


  loadRemoteComponent(id) {
    return this._getComponentVersion(id).then(component => {
      if (!component) throw new (_exceptions2().ComponentNotFound)(id.toString());
      return component.toConsumer(this.scope.objects);
    });
  }

  loadComponent(id, localOnly = true) {
    _logger().default.debugAndAddBreadCrumb('ScopeComponentsImporter', 'loadComponent {id}', {
      id: id.toString()
    });

    if (localOnly && !id.isLocal(this.scope.name)) {
      throw new (_generalError().default)('cannot load bit from remote scope, please import first');
    }

    return this.loadRemoteComponent(id);
  }
  /**
   * recursive function.
   * if found locally, use them. Otherwise, fetch from remote and then, save into the model.
   */


  _getExternalMany(ids, remotes, localFetch = true, persist = true, context = {}) {
    if (!ids.length) return Promise.resolve([]);

    _logger().default.debugAndAddBreadCrumb('scope.getExternalMan', `planning on fetching from ${localFetch ? 'local' : 'remote'} scope. Ids: {ids}`, {
      ids: ids.join(', ')
    });

    (0, _enrichContextFromGlobal().default)(Object.assign(context, {
      requestedBitIds: ids.map(id => id.toString())
    }));
    return this.sources.getMany(ids).then(defs => {
      const left = defs.filter(def => {
        if (!localFetch) return true;
        if (!def.component) return true;
        return false;
      });

      if (left.length === 0) {
        _logger().default.debugAndAddBreadCrumb('scope.getExternalMany', 'no more ids left, all found locally, exiting the method');

        return (0, _pMapSeries().default)(defs, def => this.componentToVersionDependencies(def.component, def.id));
      }

      _logger().default.debugAndAddBreadCrumb('scope.getExternalMany', `${left.length} left. Fetching them from a remote`);

      return remotes.fetch(left.map(def => def.id), this.scope, undefined, context).then(componentObjects => {
        _logger().default.debugAndAddBreadCrumb('scope.getExternalMany', 'writing them to the model');

        return this.scope.writeManyComponentsToModel(componentObjects, persist);
      }).then(() => this._getExternalMany(ids, remotes));
    });
  }
  /**
   * If the component is not in the local scope, fetch it from a remote and save into the local
   * scope. (objects directory).
   */


  _getExternal({
    id,
    remotes,
    localFetch = true,
    context = {}
  }) {
    // if (!id) return Promise.resolve();
    (0, _enrichContextFromGlobal().default)(context);
    return this.sources.get(id).then(component => {
      if (component && localFetch) {
        return this.componentToVersionDependencies(component, id);
      }

      return remotes.fetch([id], this.scope, undefined, context).then(([componentObjects]) => {
        return this.scope.writeComponentToModel(componentObjects);
      }).then(() => this._getExternal({
        id,
        remotes,
        localFetch: true
      }));
    });
  }

  _getExternalWithoutDependencies({
    id,
    remotes,
    localFetch = true,
    context = {}
  }) {
    return this.sources.get(id).then(component => {
      if (component && localFetch) {
        // $FlowFixMe scope component must have a version
        return component.toComponentVersion(id.version);
      }

      return remotes.fetch([id], this.scope, true, context).then(([componentObjects]) => this.scope.writeComponentToModel(componentObjects)).then(() => this._getExternal({
        id,
        remotes,
        localFetch: true
      })).then(versionDependencies => versionDependencies.component);
    });
  }

  _getExternalManyWithoutDependencies(ids, remotes, localFetch = false, context = {}) {
    if (!ids.length) return Promise.resolve([]);

    _logger().default.debugAndAddBreadCrumb('getExternalOnes', `getExternalOnes, ids: {ids}, localFetch: ${localFetch.toString()}`, {
      ids: ids.join(', ')
    });

    (0, _enrichContextFromGlobal().default)(Object.assign(context, {
      requestedBitIds: ids.map(id => id.toString())
    }));
    return this.sources.getMany(ids).then(defs => {
      const left = defs.filter(def => {
        if (!localFetch) return true;
        if (!def.component) return true;
        return false;
      });

      if (left.length === 0) {
        _logger().default.debugAndAddBreadCrumb('scope.getExternalOnes', 'no more ids left, all found locally, exiting the method'); // $FlowFixMe


        return Promise.all(defs.map(def => def.component.toComponentVersion(def.id.version)));
      }

      _logger().default.debugAndAddBreadCrumb('getExternalOnes', `getExternalOnes: ${left.length} left. Fetching them from a remote`);

      return remotes.fetch(left.map(def => def.id), this.scope, true, context).then(componentObjects => {
        return this.scope.writeManyComponentsToModel(componentObjects);
      }).then(() => this._getExternalManyWithoutDependencies(ids, remotes, true));
    });
  }

  _getComponentVersion(id) {
    var _this5 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!id.isLocal(_this5.scope.name)) {
        const remotes = yield (0, _scopeRemotes().getScopeRemotes)(_this5.scope);
        return _this5._getExternalWithoutDependencies({
          id,
          remotes,
          localFetch: true
        });
      }

      return _this5.sources.get(id).then(component => {
        if (!component) throw new (_exceptions2().ComponentNotFound)(id.toString()); // $FlowFixMe version is set

        return component.toComponentVersion(id.version);
      });
    })();
  }

}

exports.default = ScopeComponentsImporter;