'use strict';

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

var _regenerator;

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

var _bluebird;

function _load_bluebird() {
  return _bluebird = require('bluebird');
}

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 _path;

function _load_path() {
  return _path = _interopRequireDefault(require('path'));
}

var _pMapSeries;

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

var _bitId;

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

var _logger;

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

var _consumerComponent;

function _load_consumerComponent() {
  return _consumerComponent = _interopRequireDefault(require('./consumer-component'));
}

var _utils;

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

var _constants;

function _load_constants() {
  return _constants = require('../../constants');
}

var _dependencyResolver;

function _load_dependencyResolver() {
  return _dependencyResolver = require('./dependencies/dependency-resolver');
}

var _scopeRemotes;

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

var _models;

function _load_models() {
  return _models = require('../../scope/models');
}

var _componentsPendingImport;

function _load_componentsPendingImport() {
  return _componentsPendingImport = _interopRequireDefault(require('../component-ops/exceptions/components-pending-import'));
}

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

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

var ComponentLoader = function () {
  // cache loaded components
  function ComponentLoader(consumer) {
    _classCallCheck(this, ComponentLoader);

    this._componentsCache = {};

    this.consumer = consumer;
    this.cacheResolvedDependencies = {};
  }

  _createClass(ComponentLoader, [{
    key: 'loadMany',
    value: function () {
      var _ref = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(ids) {
        var _this = this;

        var throwOnFailure = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
        var alreadyLoadedComponents, idsToProcess, invalidComponents, driverExists, allComponents;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                (_logger || _load_logger()).default.debugAndAddBreadCrumb('ComponentLoader', 'loading consumer-components from the file-system, ids: {ids}', {
                  ids: ids.toString()
                });
                alreadyLoadedComponents = [];
                idsToProcess = [];
                invalidComponents = [];

                ids.forEach(function (id) {
                  if (!(id instanceof (_bitId || _load_bitId()).BitId)) {
                    throw new TypeError(`consumer.loadComponents expects to get BitId instances, instead, got "${typeof id}"`);
                  }
                  var idWithVersion = (0, (_utils || _load_utils()).getLatestVersionNumber)(_this.consumer.bitmapIds, id);
                  var idStr = idWithVersion.toString();
                  if (_this._componentsCache[idStr]) {
                    (_logger || _load_logger()).default.debugAndAddBreadCrumb('ComponentLoader', 'the component {idStr} has been already loaded, use the cached component', { idStr });
                    alreadyLoadedComponents.push(_this._componentsCache[idStr]);
                  } else {
                    idsToProcess.push(idWithVersion);
                  }
                });

                if (idsToProcess.length) {
                  _context2.next = 7;
                  break;
                }

                return _context2.abrupt('return', { components: alreadyLoadedComponents, invalidComponents });

              case 7:
                driverExists = this.consumer.warnForMissingDriver('Warning: Bit is not be able calculate the dependencies tree. Please install bit-{lang} driver and run tag again.');
                allComponents = [];
                _context2.next = 11;
                return (0, (_pMapSeries || _load_pMapSeries()).default)(idsToProcess, function () {
                  var _ref2 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(id) {
                    var component;
                    return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
                      while (1) {
                        switch (_context.prev = _context.next) {
                          case 0:
                            _context.next = 2;
                            return _this.loadOne(id, throwOnFailure, driverExists, invalidComponents);

                          case 2:
                            component = _context.sent;

                            if (component) {
                              _this._componentsCache[component.id.toString()] = component;
                              (_logger || _load_logger()).default.debugAndAddBreadCrumb('ComponentLoader', 'Finished loading the component "{id}"', {
                                id: component.id.toString()
                              });
                              allComponents.push(component);
                            }

                          case 4:
                          case 'end':
                            return _context.stop();
                        }
                      }
                    }, _callee, _this);
                  }));

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

              case 11:
                return _context2.abrupt('return', { components: allComponents.concat(alreadyLoadedComponents), invalidComponents });

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

      function loadMany(_x) {
        return _ref.apply(this, arguments);
      }

      return loadMany;
    }()
  }, {
    key: 'loadOne',
    value: function () {
      var _ref3 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4(id, throwOnFailure, driverExists, invalidComponents) {
        var _this2 = this;

        var componentMap, bitDir, componentFromModel, component, loadDependencies;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                componentMap = this.consumer.bitMap.getComponent(id);
                bitDir = this.consumer.getPath();

                if (componentMap.rootDir) {
                  bitDir = (_path || _load_path()).default.join(bitDir, componentMap.rootDir);
                }
                _context4.next = 5;
                return this.consumer.loadComponentFromModelIfExist(id);

              case 5:
                componentFromModel = _context4.sent;
                component = void 0;
                _context4.prev = 7;
                _context4.next = 10;
                return (_consumerComponent || _load_consumerComponent()).default.loadFromFileSystem({
                  bitDir,
                  componentMap,
                  id,
                  consumer: this.consumer,
                  componentFromModel
                });

              case 10:
                component = _context4.sent;
                _context4.next = 22;
                break;

              case 13:
                _context4.prev = 13;
                _context4.t0 = _context4['catch'](7);

                if (!throwOnFailure) {
                  _context4.next = 17;
                  break;
                }

                throw _context4.t0;

              case 17:

                (_logger || _load_logger()).default.errorAndAddBreadCrumb('component-loader.loadOne', 'failed loading {id} from the file-system', {
                  id: id.toString()
                });

                if (!(_consumerComponent || _load_consumerComponent()).default.isComponentInvalidByErrorType(_context4.t0)) {
                  _context4.next = 21;
                  break;
                }

                invalidComponents.push({ id, error: _context4.t0 });
                return _context4.abrupt('return', null);

              case 21:
                throw _context4.t0;

              case 22:
                component.loadedFromFileSystem = true;
                component.originallySharedDir = componentMap.originallySharedDir || null;
                component.wrapDir = componentMap.wrapDir || null;
                // reload component map as it may be changed after calling Component.loadFromFileSystem()
                component.componentMap = this.consumer.bitMap.getComponent(id);
                component.componentFromModel = componentFromModel;
                _context4.next = 29;
                return this._handleOutOfSyncScenarios(component);

              case 29:
                if (!(!driverExists || componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED)) {
                  _context4.next = 31;
                  break;
                }

                return _context4.abrupt('return', component);

              case 31:
                loadDependencies = function () {
                  var _ref4 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3() {
                    var dependencyResolver;
                    return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
                      while (1) {
                        switch (_context3.prev = _context3.next) {
                          case 0:
                            dependencyResolver = new (_dependencyResolver || _load_dependencyResolver()).DependencyResolver(component, _this2.consumer, id);
                            _context3.next = 3;
                            return dependencyResolver.loadDependenciesForComponent(bitDir, _this2.cacheResolvedDependencies);

                          case 3:
                            (0, (_dependencyResolver || _load_dependencyResolver()).updateDependenciesVersions)(_this2.consumer, component);

                          case 4:
                          case 'end':
                            return _context3.stop();
                        }
                      }
                    }, _callee3, _this2);
                  }));

                  return function loadDependencies() {
                    return _ref4.apply(this, arguments);
                  };
                }();

                _context4.next = 34;
                return loadDependencies();

              case 34:
                return _context4.abrupt('return', component);

              case 35:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this, [[7, 13]]);
      }));

      function loadOne(_x4, _x5, _x6, _x7) {
        return _ref3.apply(this, arguments);
      }

      return loadOne;
    }()
  }, {
    key: '_handleOutOfSyncScenarios',
    value: function () {
      var _ref5 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5(component) {
        var componentFromModel, componentMap, currentId, newId, modelComponent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                componentFromModel = component.componentFromModel, componentMap = component.componentMap;
                // $FlowFixMe componentMap is set here

                currentId = componentMap.id;
                newId = void 0;

                if (componentFromModel && !currentId.hasVersion()) {
                  // component is in the scope but .bitmap doesn't have version, sync .bitmap with the scope data
                  newId = currentId.changeVersion(componentFromModel.version);
                  if (componentFromModel.scope) newId = newId.changeScope(componentFromModel.scope);
                }
                if (componentFromModel && componentFromModel.scope && currentId.hasVersion() && !currentId.hasScope()) {
                  // component is not exported in .bitmap but exported in the scope, sync .bitmap with the scope data
                  newId = currentId.changeScope(componentFromModel.scope);
                }

                if (!(!componentFromModel && currentId.hasVersion())) {
                  _context5.next = 19;
                  break;
                }

                _context5.next = 8;
                return this.consumer.scope.getModelComponentIfExist(currentId.changeVersion(null));

              case 8:
                modelComponent = _context5.sent;

                if (!modelComponent) {
                  _context5.next = 18;
                  break;
                }

                _context5.next = 12;
                return this._throwPendingImportIfNeeded(currentId);

              case 12:
                newId = currentId.changeVersion(modelComponent.latest());
                _context5.next = 15;
                return this.consumer.loadComponentFromModelIfExist(newId);

              case 15:
                component.componentFromModel = _context5.sent;
                _context5.next = 19;
                break;

              case 18:
                if (!currentId.hasScope()) {
                  // the scope doesn't have this component and .bitmap doesn't have scope, assume it's new
                  newId = currentId.changeVersion(null);
                }

              case 19:

                if (newId) {
                  component.version = newId.version;
                  component.scope = newId.scope;
                  this.consumer.bitMap.updateComponentId(newId);
                  component.componentMap = this.consumer.bitMap.getComponent(newId);
                }

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

      function _handleOutOfSyncScenarios(_x8) {
        return _ref5.apply(this, arguments);
      }

      return _handleOutOfSyncScenarios;
    }()
  }, {
    key: '_throwPendingImportIfNeeded',
    value: function () {
      var _ref6 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee6(currentId) {
        var remoteComponent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee6$(_context6) {
          while (1) {
            switch (_context6.prev = _context6.next) {
              case 0:
                if (!currentId.hasScope()) {
                  _context6.next = 6;
                  break;
                }

                _context6.next = 3;
                return this._getRemoteComponent(currentId);

              case 3:
                remoteComponent = _context6.sent;

                if (!(remoteComponent && remoteComponent.hasVersion(currentId.version))) {
                  _context6.next = 6;
                  break;
                }

                throw new (_componentsPendingImport || _load_componentsPendingImport()).default();

              case 6:
              case 'end':
                return _context6.stop();
            }
          }
        }, _callee6, this);
      }));

      function _throwPendingImportIfNeeded(_x9) {
        return _ref6.apply(this, arguments);
      }

      return _throwPendingImportIfNeeded;
    }()
  }, {
    key: '_getRemoteComponent',
    value: function () {
      var _ref7 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee7(id) {
        var remotes, componentsObjects, remoteComponent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee7$(_context7) {
          while (1) {
            switch (_context7.prev = _context7.next) {
              case 0:
                _context7.next = 2;
                return (0, (_scopeRemotes || _load_scopeRemotes()).getScopeRemotes)(this.consumer.scope);

              case 2:
                remotes = _context7.sent;
                componentsObjects = void 0;
                _context7.prev = 4;
                _context7.next = 7;
                return remotes.fetch([id], this.consumer.scope, false);

              case 7:
                componentsObjects = _context7.sent;
                _context7.next = 13;
                break;

              case 10:
                _context7.prev = 10;
                _context7.t0 = _context7['catch'](4);
                return _context7.abrupt('return', null);

              case 13:
                _context7.next = 15;
                return componentsObjects[0].toObjectsAsync(this.consumer.scope.objects);

              case 15:
                remoteComponent = _context7.sent;
                return _context7.abrupt('return', remoteComponent.component);

              case 17:
              case 'end':
                return _context7.stop();
            }
          }
        }, _callee7, this, [[4, 10]]);
      }));

      function _getRemoteComponent(_x10) {
        return _ref7.apply(this, arguments);
      }

      return _getRemoteComponent;
    }()
  }], [{
    key: 'getInstance',
    value: function getInstance(consumer) {
      return new ComponentLoader(consumer);
    }
  }]);

  return ComponentLoader;
}();

exports.default = ComponentLoader;