"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 _defineProperty2() {
  const data = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

  _defineProperty2 = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

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

  _pMapSeries = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

function _consumerComponent() {
  const data = _interopRequireDefault(require("./consumer-component"));

  _consumerComponent = function () {
    return data;
  };

  return data;
}

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

  _utils = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("../../constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _dependencyResolver() {
  const data = require("./dependencies/dependency-resolver");

  _dependencyResolver = function () {
    return data;
  };

  return data;
}

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

  _scopeRemotes = function () {
    return data;
  };

  return data;
}

function _models() {
  const data = require("../../scope/models");

  _models = function () {
    return data;
  };

  return data;
}

function _componentsPendingImport() {
  const data = _interopRequireDefault(require("../component-ops/exceptions/components-pending-import"));

  _componentsPendingImport = function () {
    return data;
  };

  return data;
}

class ComponentLoader {
  // cache loaded components
  // specific platforms may need to parse the entire project. (was used for Angular, currently not in use)
  constructor(consumer) {
    (0, _defineProperty2().default)(this, "_componentsCache", {});
    (0, _defineProperty2().default)(this, "_componentsCacheForCapsule", {});
    this.consumer = consumer;
    this.cacheResolvedDependencies = {};
  }

  loadForCapsule(id) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const idWithVersion = (0, _utils().getLatestVersionNumber)(_this.consumer.bitmapIds, id);
      const idStr = idWithVersion.toString();

      if (_this._componentsCacheForCapsule[idStr]) {
        return _this._componentsCacheForCapsule[idStr];
      }

      const {
        components
      } = yield _this.loadMany(_bitId().BitIds.fromArray([id]));
      const component = components[0].clone();
      _this._componentsCacheForCapsule[idStr] = component;
      return component;
    })();
  }

  loadMany(ids, throwOnFailure = true) {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debugAndAddBreadCrumb('ComponentLoader', 'loading consumer-components from the file-system, ids: {ids}', {
        ids: ids.toString()
      });

      const alreadyLoadedComponents = [];
      const idsToProcess = [];
      const invalidComponents = [];
      ids.forEach(id => {
        if (!(id instanceof _bitId().BitId)) {
          throw new TypeError(`consumer.loadComponents expects to get BitId instances, instead, got "${typeof id}"`);
        }

        const idWithVersion = (0, _utils().getLatestVersionNumber)(_this2.consumer.bitmapIds, id);
        const idStr = idWithVersion.toString();

        if (_this2._componentsCache[idStr]) {
          _logger().default.debugAndAddBreadCrumb('ComponentLoader', 'the component {idStr} has been already loaded, use the cached component', {
            idStr
          });

          alreadyLoadedComponents.push(_this2._componentsCache[idStr]);
        } else {
          idsToProcess.push(idWithVersion);
        }
      });
      if (!idsToProcess.length) return {
        components: alreadyLoadedComponents,
        invalidComponents
      };

      const driverExists = _this2.consumer.warnForMissingDriver('Warning: Bit is not be able calculate the dependencies tree. Please install bit-{lang} driver and run tag again.');

      const allComponents = [];
      yield (0, _pMapSeries().default)(idsToProcess,
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* (id) {
          const component = yield _this2.loadOne(id, throwOnFailure, driverExists, invalidComponents);

          if (component) {
            _this2._componentsCache[component.id.toString()] = component;

            _logger().default.debugAndAddBreadCrumb('ComponentLoader', 'Finished loading the component "{id}"', {
              id: component.id.toString()
            });

            allComponents.push(component);
          }
        });

        return function (_x) {
          return _ref.apply(this, arguments);
        };
      }());
      return {
        components: allComponents.concat(alreadyLoadedComponents),
        invalidComponents
      };
    })();
  }

  loadOne(id, throwOnFailure, driverExists, invalidComponents) {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      const componentMap = _this3.consumer.bitMap.getComponent(id);

      let bitDir = _this3.consumer.getPath();

      if (componentMap.rootDir) {
        bitDir = _path().default.join(bitDir, componentMap.rootDir);
      }

      const componentFromModel = yield _this3.consumer.loadComponentFromModelIfExist(id);
      let component;

      try {
        component = yield _consumerComponent().default.loadFromFileSystem({
          bitDir,
          componentMap,
          id,
          consumer: _this3.consumer,
          componentFromModel
        });
      } catch (err) {
        if (throwOnFailure) throw err;

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

        if (_consumerComponent().default.isComponentInvalidByErrorType(err)) {
          invalidComponents.push({
            id,
            error: err
          });
          return null;
        }

        throw err;
      }

      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 = _this3.consumer.bitMap.getComponent(id);
      component.componentFromModel = componentFromModel;
      yield _this3._handleOutOfSyncScenarios(component);

      if (!driverExists) {
        // no need to resolve dependencies
        return component;
      }

      const loadDependencies =
      /*#__PURE__*/
      function () {
        var _ref2 = (0, _bluebird().coroutine)(function* () {
          const dependencyResolver = new (_dependencyResolver().DependencyResolver)(component, _this3.consumer, id);
          yield dependencyResolver.loadDependenciesForComponent(bitDir, _this3.cacheResolvedDependencies, _this3.cacheProjectAst);
          (0, _dependencyResolver().updateDependenciesVersions)(_this3.consumer, component);
        });

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

      yield loadDependencies();
      return component;
    })();
  }

  _handleOutOfSyncScenarios(component) {
    var _this4 = this;

    return (0, _bluebird().coroutine)(function* () {
      const {
        componentFromModel,
        componentMap
      } = component; // $FlowFixMe componentMap is set here

      const currentId = componentMap.id;
      let newId;

      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()) {
        // the version used in .bitmap doesn't exist in the scope
        const modelComponent = yield _this4.consumer.scope.getModelComponentIfExist(currentId.changeVersion(null));

        if (modelComponent) {
          // the scope has this component but not the version used in .bitmap, sync .bitmap with
          // latest version from the scope
          yield _this4._throwPendingImportIfNeeded(currentId);
          newId = currentId.changeVersion(modelComponent.latest());
          component.componentFromModel = yield _this4.consumer.loadComponentFromModelIfExist(newId);
        } else if (!currentId.hasScope()) {
          // the scope doesn't have this component and .bitmap doesn't have scope, assume it's new
          newId = currentId.changeVersion(null);
        }
      }

      if (newId) {
        component.version = newId.version;
        component.scope = newId.scope;

        _this4.consumer.bitMap.updateComponentId(newId);

        component.componentMap = _this4.consumer.bitMap.getComponent(newId);
      }
    })();
  }

  _throwPendingImportIfNeeded(currentId) {
    var _this5 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (currentId.hasScope()) {
        const remoteComponent = yield _this5._getRemoteComponent(currentId); // $FlowFixMe version is set here

        if (remoteComponent && remoteComponent.hasVersion(currentId.version)) {
          throw new (_componentsPendingImport().default)();
        }
      }
    })();
  }

  _getRemoteComponent(id) {
    var _this6 = this;

    return (0, _bluebird().coroutine)(function* () {
      const remotes = yield (0, _scopeRemotes().getScopeRemotes)(_this6.consumer.scope);
      let componentsObjects;

      try {
        componentsObjects = yield remotes.fetch([id], _this6.consumer.scope, false);
      } catch (err) {
        return null; // probably doesn't exist
      }

      const remoteComponent = yield componentsObjects[0].toObjectsAsync(_this6.consumer.scope.objects);
      return remoteComponent.component;
    })();
  }

  _isAngularProject() {
    return Boolean(this.consumer.config.packageJsonObject && this.consumer.config.packageJsonObject.dependencies && this.consumer.config.packageJsonObject.dependencies[_constants().ANGULAR_PACKAGE_IDENTIFIER]);
  }

  static getInstance(consumer) {
    return new ComponentLoader(consumer);
  }

}

exports.default = ComponentLoader;