'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _regenerator;

function _load_regenerator() {
  return _regenerator = _interopRequireDefault(require('babel-runtime/regenerator'));
}

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _ramda;

function _load_ramda() {
  return _ramda = _interopRequireDefault(require('ramda'));
}

var _pMapSeries;

function _load_pMapSeries() {
  return _pMapSeries = _interopRequireDefault(require('p-map-series'));
}

var _remotes;

function _load_remotes() {
  return _remotes = require('../../remotes');
}

var _enrichContextFromGlobal;

function _load_enrichContextFromGlobal() {
  return _enrichContextFromGlobal = _interopRequireDefault(require('../../hooks/utils/enrich-context-from-global'));
}

var _bitId;

function _load_bitId() {
  return _bitId = require('../../bit-id');
}

var _versionDependencies;

function _load_versionDependencies() {
  return _versionDependencies = _interopRequireDefault(require('../version-dependencies'));
}

var _logger;

function _load_logger() {
  return _logger = _interopRequireDefault(require('../../logger/logger'));
}

var _exceptions;

function _load_exceptions() {
  return _exceptions = require('../network/exceptions');
}

var _exceptions2;

function _load_exceptions2() {
  return _exceptions2 = require('../exceptions');
}

var _analytics;

function _load_analytics() {
  return _analytics = require('../../analytics/analytics');
}

var _sources;

function _load_sources() {
  return _sources = _interopRequireDefault(require('../repositories/sources'));
}

var _generalError;

function _load_generalError() {
  return _generalError = _interopRequireDefault(require('../../error/general-error'));
}

var _scopeRemotes;

function _load_scopeRemotes() {
  return _scopeRemotes = require('../scope-remotes');
}

var _utils;

function _load_utils() {
  return _utils = require('../../utils');
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var removeNils = (_ramda || _load_ramda()).default.reject((_ramda || _load_ramda()).default.isNil);

var ScopeComponentsImporter = function () {
  function ScopeComponentsImporter(scope) {
    _classCallCheck(this, ScopeComponentsImporter);

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

  _createClass(ScopeComponentsImporter, [{
    key: 'importMany',


    /**
     * 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).
     */
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(ids) {
        var _this = this;

        var cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
        var persist = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;

        var idsWithoutNils, _R$splitWhen, _R$splitWhen2, externals, locals, localDefs, versionDeps, remotes, externalDeps;

        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                (_logger || _load_logger()).default.debugAndAddBreadCrumb('ScopeComponentsImporter', 'importMany: {ids}', { ids: ids.toString() });
                idsWithoutNils = removeNils(ids);

                if (!(_ramda || _load_ramda()).default.isEmpty(idsWithoutNils)) {
                  _context.next = 4;
                  break;
                }

                return _context.abrupt('return', Promise.resolve([]));

              case 4:
                _R$splitWhen = (_ramda || _load_ramda()).default.splitWhen(function (id) {
                  return id.isLocal(_this.scope.name);
                }, idsWithoutNils), _R$splitWhen2 = _slicedToArray(_R$splitWhen, 2), externals = _R$splitWhen2[0], locals = _R$splitWhen2[1];
                _context.next = 7;
                return this.sources.getMany(locals);

              case 7:
                localDefs = _context.sent;
                _context.next = 10;
                return (0, (_pMapSeries || _load_pMapSeries()).default)(localDefs, function (def) {
                  if (!def.component) throw new (_exceptions2 || _load_exceptions2()).ComponentNotFound(def.id.toString());
                  return _this.componentToVersionDependencies(def.component, def.id);
                });

              case 10:
                versionDeps = _context.sent;

                (_logger || _load_logger()).default.debugAndAddBreadCrumb('ScopeComponentsImporter', 'importMany: successfully fetched local components and their dependencies. Going to fetch externals');
                _context.next = 14;
                return (0, (_scopeRemotes || _load_scopeRemotes()).getScopeRemotes)(this.scope);

              case 14:
                remotes = _context.sent;
                _context.next = 17;
                return this._getExternalMany(externals, remotes, cache, persist);

              case 17:
                externalDeps = _context.sent;
                return _context.abrupt('return', versionDeps.concat(externalDeps));

              case 19:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

      function importMany(_x3) {
        return _ref.apply(this, arguments);
      }

      return importMany;
    }()
  }, {
    key: 'importManyWithoutDependencies',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(ids) {
        var _this2 = this;

        var cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

        var idsWithoutNils, _splitBy, _splitBy2, externals, locals, localDefs, componentVersionArr, remotes, externalDeps;

        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                if (ids.length) {
                  _context2.next = 2;
                  break;
                }

                return _context2.abrupt('return', []);

              case 2:
                (_logger || _load_logger()).default.debug(`scope.importManyOnes. Ids: ${ids.join(', ')}, cache: ${cache.toString()}`);
                (_analytics || _load_analytics()).Analytics.addBreadCrumb('importManyOnes', `scope.importManyOnes. Ids: ${(_analytics || _load_analytics()).Analytics.hashData(ids)}`);

                idsWithoutNils = removeNils(ids);

                if (!(_ramda || _load_ramda()).default.isEmpty(idsWithoutNils)) {
                  _context2.next = 7;
                  break;
                }

                return _context2.abrupt('return', Promise.resolve([]));

              case 7:
                _splitBy = (0, (_utils || _load_utils()).splitBy)(idsWithoutNils, function (id) {
                  return id.isLocal(_this2.scope.name);
                }), _splitBy2 = _slicedToArray(_splitBy, 2), externals = _splitBy2[0], locals = _splitBy2[1];
                _context2.next = 10;
                return this.sources.getMany(locals);

              case 10:
                localDefs = _context2.sent;
                _context2.next = 13;
                return Promise.all(localDefs.map(function (def) {
                  if (!def.component) throw new (_exceptions2 || _load_exceptions2()).ComponentNotFound(def.id.toString());
                  // $FlowFixMe it must have a version
                  return def.component.toComponentVersion(def.id.version);
                }));

              case 13:
                componentVersionArr = _context2.sent;
                _context2.next = 16;
                return (0, (_scopeRemotes || _load_scopeRemotes()).getScopeRemotes)(this.scope);

              case 16:
                remotes = _context2.sent;
                _context2.next = 19;
                return this._getExternalManyWithoutDependencies(externals, remotes, cache);

              case 19:
                externalDeps = _context2.sent;
                return _context2.abrupt('return', componentVersionArr.concat(externalDeps));

              case 21:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this);
      }));

      function importManyWithoutDependencies(_x5) {
        return _ref2.apply(this, arguments);
      }

      return importManyWithoutDependencies;
    }()

    /**
     * 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.
     */

  }, {
    key: 'importManyWithAllVersions',
    value: function () {
      var _ref3 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3(ids) {
        var _this3 = this;

        var cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
        var idsWithoutNils, versionDependenciesArr, allVersionsP;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                (_logger || _load_logger()).default.debug(`scope.getManyWithAllVersions, Ids: ${ids.join(', ')}`);
                (_analytics || _load_analytics()).Analytics.addBreadCrumb('getManyWithAllVersions', `scope.getManyWithAllVersions, Ids: ${(_analytics || _load_analytics()).Analytics.hashData(ids)}`);
                idsWithoutNils = removeNils(ids);

                if (!(_ramda || _load_ramda()).default.isEmpty(idsWithoutNils)) {
                  _context3.next = 5;
                  break;
                }

                return _context3.abrupt('return', Promise.resolve([]));

              case 5:
                _context3.next = 7;
                return this.importMany(idsWithoutNils, cache);

              case 7:
                versionDependenciesArr = _context3.sent;
                allVersionsP = versionDependenciesArr.map(function (versionDependencies) {
                  var versions = versionDependencies.component.component.listVersions();
                  var idsWithAllVersions = versions.map(function (version) {
                    if (version === versionDependencies.component.version) return null; // imported already
                    var versionId = versionDependencies.component.id;
                    return versionId.changeVersion(version);
                  });
                  // $FlowFixMe
                  var bitIdsWithAllVersions = (_bitId || _load_bitId()).BitIds.fromArray(idsWithAllVersions.filter(function (x) {
                    return x;
                  }));
                  return _this3.importManyWithoutDependencies(bitIdsWithAllVersions);
                });
                _context3.next = 11;
                return Promise.all(allVersionsP);

              case 11:
                return _context3.abrupt('return', versionDependenciesArr);

              case 12:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function importManyWithAllVersions(_x7) {
        return _ref3.apply(this, arguments);
      }

      return importManyWithAllVersions;
    }()
  }, {
    key: 'importDependencies',
    value: function importDependencies(dependencies) {
      var _this4 = this;

      return new Promise(function (resolve, reject) {
        return _this4.importMany(dependencies).then(resolve).catch(function (e) {
          (_logger || _load_logger()).default.error(`importDependencies got an error: ${JSON.stringify(e)}`);
          if (e instanceof (_exceptions || _load_exceptions()).RemoteScopeNotFound || e instanceof (_exceptions || _load_exceptions()).PermissionDenied) return reject(e);
          return reject(new (_exceptions2 || _load_exceptions2()).DependencyNotFound(e.id));
        });
      });
    }
  }, {
    key: 'componentToVersionDependencies',
    value: function () {
      var _ref4 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4(component, id) {
        var _this5 = this;

        var versionComp, source, version, dependencies, devDependencies, compilerDependencies, testerDependencies;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                // $FlowFixMe
                versionComp = component.toComponentVersion(id.version);
                // $FlowFixMe

                source = id.scope;
                _context4.next = 4;
                return versionComp.getVersion(this.scope.objects);

              case 4:
                version = _context4.sent;

                if (version) {
                  _context4.next = 10;
                  break;
                }

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

                if (!(component.scope === this.scope.name)) {
                  _context4.next = 9;
                  break;
                }

                throw new (_generalError || _load_generalError()).default(`Version ${versionComp.version} of ${component.id().toString()} was not found in scope ${this.scope.name}`);

              case 9:
                return _context4.abrupt('return', (0, (_scopeRemotes || _load_scopeRemotes()).getScopeRemotes)(this.scope).then(function (remotes) {
                  return _this5._getExternal({ id, remotes, localFetch: false });
                }));

              case 10:

                (_logger || _load_logger()).default.debug(`toVersionDependencies, component ${component.id().toString()}, version ${versionComp.version} found, going to collect its dependencies`);
                _context4.next = 13;
                return this.importManyWithoutDependencies(version.flattenedDependencies);

              case 13:
                dependencies = _context4.sent;
                _context4.next = 16;
                return this.importManyWithoutDependencies(version.flattenedDevDependencies);

              case 16:
                devDependencies = _context4.sent;
                _context4.next = 19;
                return this.importManyWithoutDependencies(version.flattenedCompilerDependencies);

              case 19:
                compilerDependencies = _context4.sent;
                _context4.next = 22;
                return this.importManyWithoutDependencies(version.flattenedTesterDependencies);

              case 22:
                testerDependencies = _context4.sent;
                return _context4.abrupt('return', new (_versionDependencies || _load_versionDependencies()).default(versionComp, dependencies, devDependencies, compilerDependencies, testerDependencies, source));

              case 24:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this);
      }));

      function componentToVersionDependencies(_x8, _x9) {
        return _ref4.apply(this, arguments);
      }

      return componentToVersionDependencies;
    }()
  }, {
    key: 'componentsToComponentsObjects',
    value: function componentsToComponentsObjects(components) {
      var _this6 = this;

      return Promise.all(components.map(function (component) {
        return component.toObjects(_this6.scope.objects);
      }));
    }

    /**
     * get ConsumerComponent by bitId. if the component was not found locally, import it from a remote scope
     */

  }, {
    key: 'loadRemoteComponent',
    value: function loadRemoteComponent(id) {
      var _this7 = this;

      return this._getComponentVersion(id).then(function (component) {
        if (!component) throw new (_exceptions2 || _load_exceptions2()).ComponentNotFound(id.toString());
        return component.toConsumer(_this7.scope.objects);
      });
    }
  }, {
    key: 'loadComponent',
    value: function loadComponent(id) {
      var localOnly = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      (_logger || _load_logger()).default.debugAndAddBreadCrumb('ScopeComponentsImporter', 'loadComponent {id}', { id: id.toString() });

      if (localOnly && !id.isLocal(this.scope.name)) {
        throw new (_generalError || _load_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.
     */

  }, {
    key: '_getExternalMany',
    value: function _getExternalMany(ids, remotes) {
      var localFetch = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;

      var _this8 = this;

      var persist = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
      var context = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};

      if (!ids.length) return Promise.resolve([]);
      (_logger || _load_logger()).default.debugAndAddBreadCrumb('scope.getExternalMan', `planning on fetching from ${localFetch ? 'local' : 'remote'} scope. Ids: {ids}`, { ids: ids.join(', ') });
      (0, (_enrichContextFromGlobal || _load_enrichContextFromGlobal()).default)(Object.assign(context, { requestedBitIds: ids.map(function (id) {
          return id.toString();
        }) }));
      return this.sources.getMany(ids).then(function (defs) {
        var left = defs.filter(function (def) {
          if (!localFetch) return true;
          if (!def.component) return true;
          return false;
        });

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

          return Promise.all(
          // $FlowFixMe - there should be a component because there no defs without components left.
          defs.map(function (def) {
            return _this8.componentToVersionDependencies(def.component, def.id);
          }));
        }

        (_logger || _load_logger()).default.debugAndAddBreadCrumb('scope.getExternalMany', `${left.length} left. Fetching them from a remote`);
        return remotes.fetch(left.map(function (def) {
          return def.id;
        }), _this8.scope, undefined, context).then(function (componentObjects) {
          (_logger || _load_logger()).default.debugAndAddBreadCrumb('scope.getExternalMany', 'writing them to the model');
          return _this8.scope.writeManyComponentsToModel(componentObjects, persist);
        }).then(function () {
          return _this8._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).
     */

  }, {
    key: '_getExternal',
    value: function _getExternal(_ref5) {
      var _this9 = this;

      var id = _ref5.id,
          remotes = _ref5.remotes,
          _ref5$localFetch = _ref5.localFetch,
          localFetch = _ref5$localFetch === undefined ? true : _ref5$localFetch,
          _ref5$context = _ref5.context,
          context = _ref5$context === undefined ? {} : _ref5$context;

      // if (!id) return Promise.resolve();
      (0, (_enrichContextFromGlobal || _load_enrichContextFromGlobal()).default)(context);
      return this.sources.get(id).then(function (component) {
        if (component && localFetch) {
          return _this9.componentToVersionDependencies(component, id);
        }

        return remotes.fetch([id], _this9.scope, undefined, context).then(function (_ref6) {
          var _ref7 = _slicedToArray(_ref6, 1),
              componentObjects = _ref7[0];

          return _this9.scope.writeComponentToModel(componentObjects);
        }).then(function () {
          return _this9._getExternal({ id, remotes, localFetch: true });
        });
      });
    }
  }, {
    key: '_getExternalWithoutDependencies',
    value: function _getExternalWithoutDependencies(_ref8) {
      var _this10 = this;

      var id = _ref8.id,
          remotes = _ref8.remotes,
          _ref8$localFetch = _ref8.localFetch,
          localFetch = _ref8$localFetch === undefined ? true : _ref8$localFetch,
          _ref8$context = _ref8.context,
          context = _ref8$context === undefined ? {} : _ref8$context;

      return this.sources.get(id).then(function (component) {
        if (component && localFetch) {
          // $FlowFixMe scope component must have a version
          return component.toComponentVersion(id.version);
        }
        return remotes.fetch([id], _this10.scope, true, context).then(function (_ref9) {
          var _ref10 = _slicedToArray(_ref9, 1),
              componentObjects = _ref10[0];

          return _this10.scope.writeComponentToModel(componentObjects);
        }).then(function () {
          return _this10._getExternal({ id, remotes, localFetch: true });
        }).then(function (versionDependencies) {
          return versionDependencies.component;
        });
      });
    }
  }, {
    key: '_getExternalManyWithoutDependencies',
    value: function _getExternalManyWithoutDependencies(ids, remotes) {
      var _this11 = this;

      var localFetch = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
      var context = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};

      if (!ids.length) return Promise.resolve([]);
      (_logger || _load_logger()).default.debugAndAddBreadCrumb('getExternalOnes', `getExternalOnes, ids: {ids}, localFetch: ${localFetch.toString()}`, { ids: ids.join(', ') });
      (0, (_enrichContextFromGlobal || _load_enrichContextFromGlobal()).default)(Object.assign(context, { requestedBitIds: ids.map(function (id) {
          return id.toString();
        }) }));
      return this.sources.getMany(ids).then(function (defs) {
        var left = defs.filter(function (def) {
          if (!localFetch) return true;
          if (!def.component) return true;
          return false;
        });

        if (left.length === 0) {
          (_logger || _load_logger()).default.debugAndAddBreadCrumb('scope.getExternalOnes', 'no more ids left, all found locally, exiting the method');
          // $FlowFixMe
          return Promise.all(defs.map(function (def) {
            return def.component.toComponentVersion(def.id.version);
          }));
        }

        (_logger || _load_logger()).default.debugAndAddBreadCrumb('getExternalOnes', `getExternalOnes: ${left.length} left. Fetching them from a remote`);
        return remotes.fetch(left.map(function (def) {
          return def.id;
        }), _this11.scope, true, context).then(function (componentObjects) {
          return _this11.scope.writeManyComponentsToModel(componentObjects);
        }).then(function () {
          return _this11._getExternalManyWithoutDependencies(ids, remotes, true);
        });
      });
    }
  }, {
    key: '_getComponentVersion',
    value: function () {
      var _ref11 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5(id) {
        var remotes;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                if (id.isLocal(this.scope.name)) {
                  _context5.next = 5;
                  break;
                }

                _context5.next = 3;
                return (0, (_scopeRemotes || _load_scopeRemotes()).getScopeRemotes)(this.scope);

              case 3:
                remotes = _context5.sent;
                return _context5.abrupt('return', this._getExternalWithoutDependencies({ id, remotes, localFetch: true }));

              case 5:
                return _context5.abrupt('return', this.sources.get(id).then(function (component) {
                  if (!component) throw new (_exceptions2 || _load_exceptions2()).ComponentNotFound(id.toString());
                  // $FlowFixMe version is set
                  return component.toComponentVersion(id.version);
                }));

              case 6:
              case 'end':
                return _context5.stop();
            }
          }
        }, _callee5, this);
      }));

      function _getComponentVersion(_x16) {
        return _ref11.apply(this, arguments);
      }

      return _getComponentVersion;
    }()
  }], [{
    key: 'getInstance',
    value: function getInstance(scope) {
      return new ScopeComponentsImporter(scope);
    }
  }]);

  return ScopeComponentsImporter;
}();

exports.default = ScopeComponentsImporter;