'use strict';

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

var _regenerator;

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

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

function _load_stringFormat() {
  return _stringFormat = _interopRequireDefault(require('string-format'));
}

var _fsExtra;

function _load_fsExtra() {
  return _fsExtra = _interopRequireDefault(require('fs-extra'));
}

var _ramda;

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

var _commentJson;

function _load_commentJson() {
  return _commentJson = _interopRequireDefault(require('comment-json'));
}

var _logger;

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

var _constants;

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

var _exceptions;

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

var _bitId;

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

var _utils;

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

var _componentMap;

function _load_componentMap() {
  return _componentMap = _interopRequireDefault(require('./component-map'));
}

var _generalError;

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

var _invalidConfigDir;

function _load_invalidConfigDir() {
  return _invalidConfigDir = _interopRequireDefault(require('./exceptions/invalid-config-dir'));
}

var _bitJson;

function _load_bitJson() {
  return _bitJson = _interopRequireDefault(require('../bit-json'));
}

var _compilerExtension;

function _load_compilerExtension() {
  return _compilerExtension = require('../../extensions/compiler-extension');
}

var _testerExtension;

function _load_testerExtension() {
  return _testerExtension = require('../../extensions/tester-extension');
}

var _configDir;

function _load_configDir() {
  return _configDir = _interopRequireDefault(require('./config-dir'));
}

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 BitMap = function () {
  // path => componentId
  function BitMap(projectRoot, mapPath, version) {
    var _this = this;

    _classCallCheck(this, BitMap);

    this._invalidateCache = function () {
      _this.paths = {};
      _this.pathsLowerCase = {};
    };

    this.projectRoot = projectRoot;
    this.mapPath = mapPath;
    this.components = {};
    this.hasChanged = false;
    this.version = version;
    this.paths = {};
    this.pathsLowerCase = {};
    this.markAsChangedBinded = this.markAsChanged.bind(this);
  } // path => componentId


  _createClass(BitMap, [{
    key: 'markAsChanged',
    value: function markAsChanged() {
      this.hasChanged = true;
      this._invalidateCache();
    }
  }, {
    key: 'setComponent',
    value: function setComponent(bitId, componentMap) {
      var id = bitId.toString();
      if (!bitId.hasVersion() && bitId.scope) {
        throw new (_generalError || _load_generalError()).default(`invalid bitmap id ${id}, a component must have a version when a scope-name is included`);
      }
      if (componentMap.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED) {
        // make sure there are no duplications (same name)
        var similarIds = this.findSimilarIds(bitId, true);
        if (similarIds.length) {
          throw new (_generalError || _load_generalError()).default(`your id ${id} is duplicated with ${similarIds.toString()}`);
        }
      }

      componentMap.id = bitId;
      this.components[id] = componentMap;
      this.markAsChanged();
    }
  }, {
    key: 'setComponentProp',
    value: function setComponentProp(id, propName, val) {
      var componentMap = this.getComponent(id, { ignoreScopeAndVersion: true });
      // $FlowFixMe
      componentMap[propName] = val;
      this.markAsChanged();
      return componentMap;
    }
  }, {
    key: 'removeComponentProp',
    value: function removeComponentProp(id, propName) {
      var componentMap = this.getComponent(id, { ignoreScopeAndVersion: true });
      // $FlowFixMe
      delete componentMap[propName];
      this.markAsChanged();
      return componentMap;
    }
  }, {
    key: 'attachEnv',
    value: function attachEnv(id, _ref) {
      var compiler = _ref.compiler,
          tester = _ref.tester;

      var componentMap = this.getComponent(id, { ignoreScopeAndVersion: true });
      // For authored component just make sure to remove the detached
      if (componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
        if (compiler) {
          this.removeComponentProp(id, 'detachedCompiler');
        }
        if (tester) {
          this.removeComponentProp(id, 'detachedTester');
        }
        return true;
      }
      // For imported components we want to set the detached to false (which will cause the env loading from the workspace bit.json)
      if (compiler) {
        this.setComponentProp(id, 'detachedCompiler', false);
      }
      if (tester) {
        this.setComponentProp(id, 'detachedTester', false);
      }
      return true;
    }
  }, {
    key: 'loadComponents',
    value: function loadComponents(componentsJson) {
      var _this2 = this;

      Object.keys(componentsJson).forEach(function (componentId) {
        var componentFromJson = componentsJson[componentId];
        var idHasScope = function idHasScope() {
          if (componentFromJson.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) return true;
          if ('exported' in componentFromJson) {
            return componentFromJson.exported;
          }
          // backward compatibility
          return (_bitId || _load_bitId()).BitId.parseObsolete(componentId).hasScope();
        };
        componentFromJson.id = (_bitId || _load_bitId()).BitId.parse(componentId, idHasScope());
        var componentMap = (_componentMap || _load_componentMap()).default.fromJson(componentsJson[componentId]);
        componentMap.setMarkAsChangedCb(_this2.markAsChangedBinded);
        _this2.components[componentId] = componentMap;
      });
    }
  }, {
    key: 'getAllComponents',
    value: function getAllComponents(origin) {
      if (!origin) return this.components;
      var isOriginMatch = function isOriginMatch(component) {
        return component.origin === origin;
      };
      // $FlowFixMe we know origin is an array in that case
      var isOriginMatchArray = function isOriginMatchArray(component) {
        return origin.includes(component.origin);
      };
      var filter = Array.isArray(origin) ? isOriginMatchArray : isOriginMatch;
      return (_ramda || _load_ramda()).default.filter(filter, this.components);
    }

    /**
     * We should ignore ejected config files and dirs
     * Files might be on the root dir then we need to ignore them directly by taking them from the bit.json
     * They might be in internal dirs then we need to ignore the dir completely
     */

  }, {
    key: 'getConfigDirsAndFilesToIgnore',
    value: function getConfigDirsAndFilesToIgnore(consumerPath) {
      var ignoreList = {
        files: [],
        dirs: []
      };
      (_ramda || _load_ramda()).default.values(this.components).forEach(function (component) {
        var configDir = component.configDir;
        var componentDir = component.getComponentDir();
        if (configDir && componentDir) {
          var resolvedBaseConfigDir = component.getBaseConfigDir() || '';
          var fullConfigDir = (_path || _load_path()).default.join(consumerPath, resolvedBaseConfigDir);
          var componentBitJson = (_bitJson || _load_bitJson()).default.loadSync(fullConfigDir);
          var compilerObj = (_ramda || _load_ramda()).default.values(componentBitJson.compiler)[0];
          var compilerFilesObj = compilerObj && compilerObj.files ? compilerObj.files : undefined;
          var testerObj = (_ramda || _load_ramda()).default.values(componentBitJson.tester)[0];
          var testerFilesObj = testerObj && testerObj.files ? testerObj.files : undefined;
          var compilerFiles = compilerFilesObj ? (_ramda || _load_ramda()).default.values(compilerFilesObj) : [];
          var testerFiles = testerFilesObj ? (_ramda || _load_ramda()).default.values(testerFilesObj) : [];
          // R.values above might return array of something which is not string
          // Which will not be ok with the input of resolveIgnoreFilesAndDirs
          var toIgnore = BitMap.resolveIgnoreFilesAndDirs(configDir.linuxDirPath, componentDir,
          // $FlowFixMe - see comment above
          compilerFiles,
          // $FlowFixMe - see comment above
          testerFiles);
          ignoreList.files = ignoreList.files.concat(toIgnore.files);
          ignoreList.dirs = ignoreList.dirs.concat(toIgnore.dirs);
        }
      });
      return ignoreList;
    }
  }, {
    key: 'getAllBitIds',
    value: function getAllBitIds(origin) {
      var allComponents = (_ramda || _load_ramda()).default.values(this.components);
      var ids = function ids(componentMaps) {
        return (_bitId || _load_bitId()).BitIds.fromArray(componentMaps.map(function (c) {
          return c.id.clone();
        }));
      };
      if (!origin) return ids(allComponents);
      // $FlowFixMe we know origin is an array in that case
      var components = allComponents.filter(function (c) {
        return origin.includes(c.origin);
      });
      return ids(components);
    }

    /**
     * get existing bitmap bit-id by bit-id.
     * throw an exception if not found
     * @see also getBitIdIfExist
     */

  }, {
    key: 'getBitId',
    value: function getBitId(bitId) {
      var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref2$ignoreVersion = _ref2.ignoreVersion,
          ignoreVersion = _ref2$ignoreVersion === undefined ? false : _ref2$ignoreVersion,
          _ref2$ignoreScopeAndV = _ref2.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref2$ignoreScopeAndV === undefined ? false : _ref2$ignoreScopeAndV;

      if (!(bitId instanceof (_bitId || _load_bitId()).BitId)) {
        throw new TypeError(`BitMap.getBitId expects bitId to be an instance of BitId, instead, got ${bitId}`);
      }
      var allIds = this.getAllBitIds();
      var exactMatch = allIds.search(bitId);
      if (exactMatch) return exactMatch;
      if (ignoreVersion) {
        var matchWithoutVersion = allIds.searchWithoutVersion(bitId);
        if (matchWithoutVersion) return matchWithoutVersion;
      }
      if (ignoreScopeAndVersion) {
        var matchWithoutScopeAndVersion = allIds.searchWithoutScopeAndVersion(bitId);
        if (matchWithoutScopeAndVersion) return matchWithoutScopeAndVersion;
      }
      throw new (_exceptions || _load_exceptions()).MissingBitMapComponent(bitId.toString());
    }

    /**
     * get existing bitmap bit-id by bit-id
     * don't throw an exception if not found
     * @see also getBitId
     */

  }, {
    key: 'getBitIdIfExist',
    value: function getBitIdIfExist(bitId) {
      var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref3$ignoreVersion = _ref3.ignoreVersion,
          ignoreVersion = _ref3$ignoreVersion === undefined ? false : _ref3$ignoreVersion,
          _ref3$ignoreScopeAndV = _ref3.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref3$ignoreScopeAndV === undefined ? false : _ref3$ignoreScopeAndV;

      try {
        var existingBitId = this.getBitId(bitId, { ignoreVersion, ignoreScopeAndVersion });
        return existingBitId;
      } catch (err) {
        if (err instanceof (_exceptions || _load_exceptions()).MissingBitMapComponent) return null;
        throw err;
      }
    }

    /**
     * get componentMap from bitmap by bit-id.
     * throw an exception if not found.
     * @see also getComponentIfExist
     */

  }, {
    key: 'getComponent',
    value: function getComponent(bitId) {
      var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref4$ignoreVersion = _ref4.ignoreVersion,
          ignoreVersion = _ref4$ignoreVersion === undefined ? false : _ref4$ignoreVersion,
          _ref4$ignoreScopeAndV = _ref4.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref4$ignoreScopeAndV === undefined ? false : _ref4$ignoreScopeAndV;

      var existingBitId = this.getBitId(bitId, { ignoreVersion, ignoreScopeAndVersion });
      return this.components[existingBitId.toString()];
    }

    /**
     * get componentMap from bitmap by bit-id
     * don't throw an exception if not found
     * @see also getComponent
     */

  }, {
    key: 'getComponentIfExist',
    value: function getComponentIfExist(bitId) {
      var _ref5 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref5$ignoreVersion = _ref5.ignoreVersion,
          ignoreVersion = _ref5$ignoreVersion === undefined ? false : _ref5$ignoreVersion,
          _ref5$ignoreScopeAndV = _ref5.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref5$ignoreScopeAndV === undefined ? false : _ref5$ignoreScopeAndV;

      try {
        var componentMap = this.getComponent(bitId, { ignoreVersion, ignoreScopeAndVersion });
        return componentMap;
      } catch (err) {
        if (err instanceof (_exceptions || _load_exceptions()).MissingBitMapComponent) return null;
        throw err;
      }
    }
  }, {
    key: 'getAuthoredExportedComponents',
    value: function getAuthoredExportedComponents() {
      var authoredIds = this.getAllBitIds([(_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED]);
      return authoredIds.filter(function (id) {
        return id.hasScope();
      });
    }
  }, {
    key: 'validateConfigDir',
    value: function validateConfigDir(compId, configDir) {
      var components = this.getAllComponents();
      if (configDir.startsWith('./')) {
        configDir = configDir.replace('./', '');
      }
      var comps = (_ramda || _load_ramda()).default.pickBy(function (component) {
        var compDir = component.getComponentDir();
        if (compDir && (0, (_utils || _load_utils()).pathIsInside)(configDir, compDir)) {
          return true;
        }
        var compConfigDir = component.configDir ? component.configDir.getResolved({ componentDir: compDir || '' }).getEnvTypeCleaned().linuxDirPath : null;
        if (compConfigDir && (0, (_utils || _load_utils()).pathIsInside)(configDir, compConfigDir)) {
          return true;
        }
        return false;
      }, components);
      if (!(_ramda || _load_ramda()).default.isEmpty(comps)) {
        var _id = (_ramda || _load_ramda()).default.keys(comps)[0];
        var stringId = (_bitId || _load_bitId()).BitId.parse(_id).toStringWithoutVersion();
        if (compId !== stringId) {
          throw new (_invalidConfigDir || _load_invalidConfigDir()).default(stringId);
        }
      }
      return true;
    }
  }, {
    key: '_makePathRelativeToProjectRoot',
    value: function _makePathRelativeToProjectRoot(pathToChange) {
      var absolutePath = (_path || _load_path()).default.resolve(pathToChange);
      return (_path || _load_path()).default.relative(this.projectRoot, absolutePath);
    }
  }, {
    key: '_searchMainFile',
    value: function _searchMainFile(baseMainFile, files, rootDir) {
      // search for an exact relative-path
      var mainFileFromFiles = files.find(function (file) {
        return file.relativePath === baseMainFile;
      });
      if (mainFileFromFiles) return baseMainFile;
      if (rootDir) {
        var mainFileUsingRootDir = files.find(function (file) {
          return (0, (_utils || _load_utils()).pathJoinLinux)(rootDir, file.relativePath) === baseMainFile;
        });
        if (mainFileUsingRootDir) return mainFileUsingRootDir.relativePath;
      }
      // search for a file-name
      var potentialMainFiles = files.filter(function (file) {
        return file.name === baseMainFile;
      });
      if (!potentialMainFiles.length) return null;
      // when there are several files that met the criteria, choose the closer to the root
      var sortByNumOfDirs = function sortByNumOfDirs(a, b) {
        return a.relativePath.split((_constants || _load_constants()).DEFAULT_SEPARATOR).length - b.relativePath.split((_constants || _load_constants()).DEFAULT_SEPARATOR).length;
      };
      potentialMainFiles.sort(sortByNumOfDirs);
      mainFileFromFiles = (_ramda || _load_ramda()).default.head(potentialMainFiles);
      return mainFileFromFiles.relativePath;
    }
  }, {
    key: '_getMainFile',
    value: function _getMainFile(mainFile, componentMap) {
      var _this3 = this;

      var files = componentMap.files.filter(function (file) {
        return !file.test;
      });
      // scenario 1) user entered mainFile => search the mainFile in the files array
      if (mainFile) {
        var foundMainFile = this._searchMainFile(mainFile, files, componentMap.rootDir);
        if (foundMainFile) return foundMainFile;
        throw new (_exceptions || _load_exceptions()).MissingMainFile(mainFile, files.map(function (file) {
          return (_path || _load_path()).default.normalize(file.relativePath);
        }));
      }
      // scenario 2) user didn't enter mainFile and the component has only one file => use that file as the main file.
      if (files.length === 1) return files[0].relativePath;
      // scenario 3) user didn't enter mainFile and the component has multiple files => search for default main files (such as index.js)
      var searchResult = void 0;
      (_constants || _load_constants()).DEFAULT_INDEX_EXTS.forEach(function (ext) {
        // TODO: can be improved - stop loop if finding main file
        if (!searchResult) {
          var mainFileNameToSearch = `${(_constants || _load_constants()).DEFAULT_INDEX_NAME}.${ext}`;
          searchResult = _this3._searchMainFile(mainFileNameToSearch, files, componentMap.rootDir);
        }
      });
      if (searchResult) return searchResult;
      var mainFileString = `${(_constants || _load_constants()).DEFAULT_INDEX_NAME}.[${(_constants || _load_constants()).DEFAULT_INDEX_EXTS.join(', ')}]`;
      throw new (_exceptions || _load_exceptions()).MissingMainFile(mainFileString, files.map(function (file) {
        return (_path || _load_path()).default.normalize(file.relativePath);
      }));
    }
  }, {
    key: 'addDependencyToParent',
    value: function addDependencyToParent(parent, dependency) {
      // the parent component might appear in bit.map file with full-id, e.g. when it is a dependency itself.
      // And it might be with the short id, without the scope and the version, e.g. when its origin is AUTHORED or IMPORTED
      var parentWithScope = parent.toString();
      var parentWithoutScope = parent.changeScope(null).toString();
      var parentId = void 0;
      if (this.components[parentWithScope]) {
        parentId = parentWithScope;
      } else if (this.components[parentWithoutScope]) {
        parentId = parentWithoutScope;
      } else {
        throw new (_generalError || _load_generalError()).default(`Unable to add indirect dependency ${dependency}, as its parent ${parent.toString()} does not exist`);
      }
      if (!this.components[parentId].dependencies) {
        this.components[parentId].dependencies = [dependency];
      }
      if (!this.components[parentId].dependencies.includes(dependency)) {
        // $FlowFixMe at this stage we know that dependencies is not null
        this.components[parentId].dependencies.push(dependency);
      }
      this.markAsChanged();
    }

    /**
     * find ids that have the same name but different version
     * if compareWithoutScope is false, the scope should be identical in addition to the name
     */

  }, {
    key: 'findSimilarIds',
    value: function findSimilarIds(id) {
      var compareWithoutScope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

      var allIds = this.getAllBitIds([(_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED, (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED]);
      var similarIds = allIds.filter(function (existingId) {
        var isSimilar = compareWithoutScope ? existingId.isEqualWithoutScopeAndVersion(id) : existingId.isEqualWithoutVersion(id);
        return isSimilar && !existingId.isEqual(id);
      });
      return (_bitId || _load_bitId()).BitIds.fromArray(similarIds);
    }
  }, {
    key: 'deleteOlderVersionsOfComponent',
    value: function deleteOlderVersionsOfComponent(componentId) {
      var _this4 = this;

      var similarIds = this.findSimilarIds(componentId);
      similarIds.forEach(function (id) {
        var idStr = id.toString();
        (_logger || _load_logger()).default.debug(`BitMap: deleting an older version ${idStr} of an existing component ${componentId.toString()}`);
        _this4._removeFromComponentsArray(id);
      });
    }

    /**
     * --- Don't use this function when you have the ID parsed. Use this.getBitId() instead ---
     *
     * id entered by the user may or may not include scope-name
     * search for a similar id in the bitmap and return the full BitId
     */

  }, {
    key: 'getExistingBitId',
    value: function getExistingBitId(id) {
      var shouldThrow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      if (!(_ramda || _load_ramda()).default.is(String, id)) {
        throw new TypeError(`BitMap.getExistingBitId expects id to be a string, instead, got ${typeof id}`);
      }
      var components = (_ramda || _load_ramda()).default.values(this.components);
      var idHasVersion = id.includes((_constants || _load_constants()).VERSION_DELIMITER);

      // start with a more strict comparison. assume the id from the user has a scope name
      var componentWithScope = components.find(function (componentMap) {
        return idHasVersion ? componentMap.id.toString() === id : componentMap.id.toStringWithoutVersion() === id;
      });
      if (componentWithScope) return componentWithScope.id.clone();

      // continue with searching without the scope name
      var idWithoutVersion = (_bitId || _load_bitId()).BitId.getStringWithoutVersion(id);
      var componentWithoutScope = components.find(function (componentMap) {
        return idHasVersion ? componentMap.id.toStringWithoutScope() === id : componentMap.id.toStringWithoutScopeAndVersion() === idWithoutVersion;
      });
      if (componentWithoutScope) return componentWithoutScope.id.clone();
      if (shouldThrow) {
        throw new (_exceptions || _load_exceptions()).MissingBitMapComponent(id);
      }
      return null;
    }

    /**
     * check if both arrays are equal according to their 'relativePath', regardless the order
     */

  }, {
    key: '_areFilesArraysEqual',
    value: function _areFilesArraysEqual(filesA, filesB) {
      if (filesA.length !== filesB.length) return false;
      var cmp = function cmp(x, y) {
        return x.relativePath === y.relativePath;
      };
      var diff = (_ramda || _load_ramda()).default.differenceWith(cmp, filesA, filesB);
      if (!diff.length) return true;
      return false;
    }

    /**
     * add files from filesB that are not in filesA
     */

  }, {
    key: 'mergeFilesArray',
    value: function mergeFilesArray(filesA, filesB) {
      return (_ramda || _load_ramda()).default.unionWith((_ramda || _load_ramda()).default.eqBy((_ramda || _load_ramda()).default.prop('relativePath')), filesA, filesB);
    }

    /**
     * if an existing file is for example uppercase and the new file is lowercase it has different
     * behavior according to the OS. some OS are case sensitive, some are not.
     * it's safer to avoid saving both files and instead, replacing the old file with the new one.
     * in case a file has replaced and it is also a mainFile, replace the mainFile as well
     */

  }, {
    key: '_updateFilesWithCurrentLetterCases',
    value: function _updateFilesWithCurrentLetterCases(componentId, newFiles) {
      var currentComponentMap = this.components[componentId];
      var currentFiles = currentComponentMap.files;
      currentFiles.forEach(function (currentFile) {
        var sameFile = newFiles.find(function (newFile) {
          return newFile.relativePath.toLowerCase() === currentFile.relativePath.toLowerCase();
        });
        if (sameFile && currentFile.relativePath !== sameFile.relativePath) {
          if (currentComponentMap.mainFile === currentFile.relativePath) {
            currentComponentMap.mainFile = sameFile.relativePath;
          }
          currentFile.relativePath = sameFile.relativePath;
        }
      });
    }
  }, {
    key: 'addComponent',
    value: function addComponent(_ref6) {
      var componentId = _ref6.componentId,
          files = _ref6.files,
          mainFile = _ref6.mainFile,
          origin = _ref6.origin,
          parent = _ref6.parent,
          rootDir = _ref6.rootDir,
          configDir = _ref6.configDir,
          trackDir = _ref6.trackDir,
          override = _ref6.override,
          detachedCompiler = _ref6.detachedCompiler,
          detachedTester = _ref6.detachedTester,
          originallySharedDir = _ref6.originallySharedDir;

      var isDependency = origin === (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED;
      var componentIdStr = componentId.toString();
      (_logger || _load_logger()).default.debug(`adding to bit.map ${componentIdStr}`);
      if (isDependency) {
        if (!parent) {
          throw new (_generalError || _load_generalError()).default(`Unable to add indirect dependency ${componentIdStr}, without "parent" parameter`);
        }
        this.addDependencyToParent(parent, componentIdStr);
      }
      if (this.components[componentIdStr]) {
        (_logger || _load_logger()).default.info(`bit.map: updating an exiting component ${componentIdStr}`);
        var existingRootDir = this.components[componentIdStr].rootDir;
        if (existingRootDir) (_componentMap || _load_componentMap()).default.changeFilesPathAccordingToItsRootDir(existingRootDir, files);
        if (override) {
          this.components[componentIdStr].files = files;
        } else {
          this._updateFilesWithCurrentLetterCases(componentIdStr, files);
          // override the current componentMap.files with the given files argument
          this.components[componentIdStr].files = (_ramda || _load_ramda()).default.unionWith((_ramda || _load_ramda()).default.eqBy((_ramda || _load_ramda()).default.prop('relativePath')), files, this.components[componentIdStr].files);
        }
        if (mainFile) {
          this.components[componentIdStr].mainFile = this._getMainFile((0, (_utils || _load_utils()).pathNormalizeToLinux)(mainFile), this.components[componentIdStr]);
        }
      } else {
        if (origin === (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED || origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
          // if there are older versions, the user is updating an existing component, delete old ones from bit.map
          this.deleteOlderVersionsOfComponent(componentId);
        }
        // $FlowFixMe not easy to fix, we can't instantiate ComponentMap with mainFile because we don't have it yet
        var componentMap = new (_componentMap || _load_componentMap()).default({ files, origin });
        componentMap.setMarkAsChangedCb(this.markAsChangedBinded);
        this.setComponent(componentId, componentMap);
        this.components[componentIdStr].mainFile = this._getMainFile((0, (_utils || _load_utils()).pathNormalizeToLinux)(mainFile), this.components[componentIdStr]);
      }
      if (rootDir) {
        // when rootDir is from the cli, it is changed to be absolute first (see write-components.writeToComponentsDir)
        // and on the next line it is changed to be relative to the project-root.
        // otherwise, rootDir may be originated from previous componentMap.rootDir value, as such,
        // when running the command from an inner directory we must not run _makePathRelativeToProjectRoot.
        var rootRelative = (_path || _load_path()).default.isAbsolute(rootDir) ? this._makePathRelativeToProjectRoot(rootDir) : rootDir;
        this.components[componentIdStr].rootDir = (0, (_utils || _load_utils()).pathNormalizeToLinux)(rootRelative);
      }
      if (configDir) {
        this.components[componentIdStr].configDir = configDir;
      }
      if (trackDir) {
        this.components[componentIdStr].trackDir = (0, (_utils || _load_utils()).pathNormalizeToLinux)(trackDir);
      }
      if (detachedCompiler) {
        this.components[componentIdStr].detachedCompiler = detachedCompiler;
      }
      if (detachedTester) {
        this.components[componentIdStr].detachedTester = detachedTester;
      }
      this.components[componentIdStr].removeTrackDirIfNeeded();
      if (originallySharedDir) {
        this.components[componentIdStr].originallySharedDir = originallySharedDir;
      }
      this.components[componentIdStr].sort();
      this.components[componentIdStr].validate();
      this.markAsChanged();
      return this.components[componentIdStr];
    }
  }, {
    key: 'addFilesToComponent',
    value: function addFilesToComponent(_ref7) {
      var componentId = _ref7.componentId,
          files = _ref7.files;

      var componentIdStr = componentId.toString();
      if (!this.components[componentIdStr]) {
        throw new (_generalError || _load_generalError()).default(`unable to add files to a non-exist component ${componentIdStr}`);
      }
      var existingRootDir = this.components[componentIdStr].rootDir;
      if (existingRootDir) (_componentMap || _load_componentMap()).default.changeFilesPathAccordingToItsRootDir(existingRootDir, files);
      if (this._areFilesArraysEqual(this.components[componentIdStr].files, files)) {
        return this.components[componentIdStr];
      }
      // do not override existing files, only add new files
      (_logger || _load_logger()).default.info(`bit.map: updating an exiting component ${componentIdStr}`);
      this.components[componentIdStr].files = (_ramda || _load_ramda()).default.unionWith((_ramda || _load_ramda()).default.eqBy((_ramda || _load_ramda()).default.prop('relativePath')), this.components[componentIdStr].files, files);
      this.components[componentIdStr].sort();
      this.components[componentIdStr].validate();
      this.markAsChanged();
      return this.components[componentIdStr];
    }
  }, {
    key: '_removeFromComponentsArray',
    value: function _removeFromComponentsArray(componentId) {
      delete this.components[componentId.toString()];
      this.markAsChanged();
    }
  }, {
    key: 'removeComponent',
    value: function removeComponent(bitId) {
      var bitmapComponent = this.getBitIdIfExist(bitId, { ignoreScopeAndVersion: true });
      if (bitmapComponent) this._removeFromComponentsArray(bitmapComponent);
      return bitmapComponent;
    }
  }, {
    key: 'removeComponents',
    value: function removeComponents(ids) {
      var _this5 = this;

      return ids.map(function (id) {
        return _this5.removeComponent(id);
      });
    }
  }, {
    key: 'addMainDistFileToComponent',
    value: function addMainDistFileToComponent(id, distFilesPaths) {
      var _this6 = this;

      if (!this.components[id]) {
        (_logger || _load_logger()).default.warn(`unable to find the component ${id} in bit.map file`);
        return;
      }
      var distFilesPathsNormalized = distFilesPaths.map(function (filePath) {
        return (0, (_utils || _load_utils()).pathNormalizeToLinux)(filePath);
      });

      var mainDistFile = distFilesPathsNormalized.find(function (distFile) {
        return distFile.endsWith(_this6.components[id].mainFile);
      });
      if (!mainDistFile) {
        (_logger || _load_logger()).default.warn(`unable to find the main dist file of component ${id}. Dist files: ${distFilesPathsNormalized.join(', ')}`);
        return;
      }
      this.components[id].mainDistFile = this._makePathRelativeToProjectRoot(mainDistFile);
      this.markAsChanged();
    }
  }, {
    key: 'setDetachedCompiler',
    value: function setDetachedCompiler(id, val) {
      if (val === null || val === undefined) {
        return this.removeComponentProp(id, 'detachedCompiler');
      }
      return this.setComponentProp(id, 'detachedCompiler', val);
    }
  }, {
    key: 'setDetachedTester',
    value: function setDetachedTester(id, val) {
      if (val === null || val === undefined) {
        return this.removeComponentProp(id, 'detachedTester');
      }
      return this.setComponentProp(id, 'detachedTester', val);
    }
  }, {
    key: 'setDetachedCompilerAndTester',
    value: function setDetachedCompilerAndTester(id, _ref8) {
      var detachedCompiler = _ref8.detachedCompiler,
          detachedTester = _ref8.detachedTester;

      this.setDetachedCompiler(id, detachedCompiler);
      return this.setDetachedTester(id, detachedTester);
    }
  }, {
    key: 'isExistWithSameVersion',
    value: function isExistWithSameVersion(id) {
      return id.hasVersion() && this.components[id.toString()];
    }

    /**
     * needed after exporting or tagging a component.
     * We don't support export/tag of nested components, only authored or imported. For authored/imported components, could be
     * in the file-system only one instance with the same component-name. As a result, we can strip the
     * scope-name and the version, find the older version in bit.map and update the id with the new one.
     */

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

      var newIdString = id.toString();
      var similarIds = this.findSimilarIds(id, true);
      if (!similarIds.length) {
        (_logger || _load_logger()).default.debug(`bit-map: no need to update ${newIdString}`);
        return;
      }
      if (similarIds.length > 1) {
        throw new (_generalError || _load_generalError()).default(`Your ${(_constants || _load_constants()).BIT_MAP} file has more than one version of ${id.toStringWithoutScopeAndVersion()} and they
      are authored or imported. This scenario is not supported`);
      }
      var olderComponentId = similarIds[0];
      var olderComponentIdStr = olderComponentId.toString();
      var olderIdStr = olderComponentId.toString();
      (_logger || _load_logger()).default.debug(`BitMap: updating an older component ${olderIdStr} with a newer component ${newIdString}`);
      var componentMap = this.components[olderIdStr];
      this._removeFromComponentsArray(olderComponentId);
      this.setComponent(id, componentMap);

      // update the dependencies array if needed
      Object.keys(this.components).forEach(function (componentId) {
        var component = _this7.components[componentId];
        if (component.dependencies && component.dependencies.includes(olderComponentIdStr)) {
          component.dependencies = component.dependencies.filter(function (dependency) {
            return dependency !== olderComponentIdStr;
          });
          component.dependencies.push(newIdString);
        }
      });
      this.markAsChanged();
    }

    /**
     * Return a potential componentMap if file is supposed to be part of it
     * by a path exist in the files object
     *
     * @param {string} componentPath relative to consumer - as stored in bit.map files object
     * @returns {ComponentMap} componentMap
     */

  }, {
    key: 'getComponentObjectOfFileByPath',
    value: function getComponentObjectOfFileByPath(componentPath) {
      var _this8 = this;

      var components = this.getAllComponents();
      return (_ramda || _load_ramda()).default.pickBy(function (component) {
        return (0, (_utils || _load_utils()).pathIsInside)(componentPath, component.rootDir || _this8.projectRoot);
      }, components);
    }

    /**
     * Return a component id as listed in bit.map file
     * by a path exist in the files object
     *
     * @param {string} componentPath relative to consumer - as stored in bit.map files object
     * @returns {BitId} component id
     * @memberof BitMap
     */

  }, {
    key: 'getComponentIdByPath',
    value: function getComponentIdByPath(componentPath) {
      var caseSensitive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      this._populateAllPaths();
      return caseSensitive ? this.paths[componentPath] : this.pathsLowerCase[componentPath.toLowerCase()];
    }
  }, {
    key: '_populateAllPaths',
    value: function _populateAllPaths() {
      var _this9 = this;

      if ((_ramda || _load_ramda()).default.isEmpty(this.paths)) {
        Object.keys(this.components).forEach(function (componentId) {
          var component = _this9.components[componentId];
          component.files.forEach(function (file) {
            var relativeToConsumer = component.rootDir ? (0, (_utils || _load_utils()).pathJoinLinux)(component.rootDir, file.relativePath) : file.relativePath;
            _this9.paths[relativeToConsumer] = component.id;
            _this9.pathsLowerCase[relativeToConsumer.toLowerCase()] = component.id;
          });
        });
      }
    }
  }, {
    key: 'updatePathLocation',
    value: function updatePathLocation(from, to, existingPath) {
      var _this10 = this;

      var isPathDir = (0, (_utils || _load_utils()).isDir)(existingPath);
      var allChanges = [];
      Object.keys(this.components).forEach(function (componentId) {
        var componentMap = _this10.components[componentId];
        var changes = isPathDir ? componentMap.updateDirLocation(from, to) : componentMap.updateFileLocation(from, to);
        if (changes && changes.length) allChanges.push({ id: componentMap.id.clone(), changes });
      });
      if ((_ramda || _load_ramda()).default.isEmpty(allChanges)) {
        var errorMsg = isPathDir ? `directory ${from} is not a tracked component` : `the file ${existingPath} is untracked`;
        throw new (_generalError || _load_generalError()).default(errorMsg);
      }

      this.markAsChanged();
      return allChanges;
    }

    /**
     * remove the id property before saving the components to the file as they are redundant with the keys
     */

  }, {
    key: 'toObjects',
    value: function toObjects() {
      var _this11 = this;

      var components = {};
      Object.keys(this.components).forEach(function (id) {
        var componentMap = _this11.components[id].clone();
        if (componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
          componentMap.exported = componentMap.id.hasScope();
        }
        delete componentMap.id;
        components[id] = componentMap.toPlainObject();
      });

      return components;
    }

    /**
     * do not call this function directly, let consumer.onDestroy() call it.
     * consumer.onDestroy() is being called (manually) at the end of the command process.
     * the risk of calling this method in other places is a parallel writing of this file, which
     * may result in a damaged file
     */

  }, {
    key: 'write',
    value: function () {
      var _ref9 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee() {
        var bitMapContent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                if (this.hasChanged) {
                  _context.next = 2;
                  break;
                }

                return _context.abrupt('return', null);

              case 2:
                (_logger || _load_logger()).default.debug('writing to bit.map');
                bitMapContent = Object.assign({}, this.toObjects(), { version: this.version });
                return _context.abrupt('return', (0, (_utils || _load_utils()).outputFile)({ filePath: this.mapPath, content: JSON.stringify(bitMapContent, null, 4) }));

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

      function write() {
        return _ref9.apply(this, arguments);
      }

      return write;
    }()
  }], [{
    key: 'load',
    value: function load(dirPath) {
      var standardLocation = (_path || _load_path()).default.join(dirPath, (_constants || _load_constants()).BIT_MAP);
      var oldLocation = (_path || _load_path()).default.join(dirPath, (_constants || _load_constants()).OLD_BIT_MAP);
      var getBitMapLocation = function getBitMapLocation() {
        if ((_fsExtra || _load_fsExtra()).default.existsSync(standardLocation)) return standardLocation;
        if ((_fsExtra || _load_fsExtra()).default.existsSync(oldLocation)) return oldLocation;
        return null;
      };
      var bitMapLocation = getBitMapLocation();
      if (!bitMapLocation) {
        (_logger || _load_logger()).default.info(`bit.map: unable to find an existing ${(_constants || _load_constants()).BIT_MAP} file. Will create a new one if needed`);
        return new BitMap(dirPath, standardLocation, (_constants || _load_constants()).BIT_VERSION);
      }
      var mapFileContent = (_fsExtra || _load_fsExtra()).default.readFileSync(bitMapLocation);
      var componentsJson = void 0;
      try {
        componentsJson = (_commentJson || _load_commentJson()).default.parse(mapFileContent.toString('utf8'), null, true);
      } catch (e) {
        (_logger || _load_logger()).default.error(e);
        throw new (_exceptions || _load_exceptions()).InvalidBitMap(bitMapLocation, e.message);
      }
      var version = componentsJson.version;
      // Don't treat version like component
      delete componentsJson.version;

      var bitMap = new BitMap(dirPath, bitMapLocation, version);
      bitMap.loadComponents(componentsJson);
      return bitMap;
    }

    /**
     * if resetHard, delete the bitMap file.
     * Otherwise, try to load it and only if the file is corrupted then delete it.
     */

  }, {
    key: 'reset',
    value: function reset(dirPath, resetHard) {
      var bitMapPath = (_path || _load_path()).default.join(dirPath, (_constants || _load_constants()).BIT_MAP);
      var deleteBitMapFile = function deleteBitMapFile() {
        (_logger || _load_logger()).default.info(`deleting the bitMap file at ${bitMapPath}`);
        (_fsExtra || _load_fsExtra()).default.removeSync(bitMapPath);
      };
      if (resetHard) {
        deleteBitMapFile();
        return;
      }
      try {
        BitMap.load(dirPath);
      } catch (err) {
        if (err instanceof (_exceptions || _load_exceptions()).InvalidBitMap) {
          deleteBitMapFile();
          return;
        }
        throw err;
      }
    }

    /**
     * Return files and dirs which need to be ignored since they are config files / dirs
     * @param {*} configDir
     * @param {*} rootDir
     * @param {*} compilerFilesPaths
     * @param {*} testerFilesPaths
     */

  }, {
    key: 'resolveIgnoreFilesAndDirs',
    value: function resolveIgnoreFilesAndDirs(configDir, rootDir) {
      var compilerFilesPaths = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
      var testerFilesPaths = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];

      var ignoreList = {
        files: [],
        dirs: []
      };
      if (!configDir) return ignoreList;
      if (configDir.startsWith(`{${(_constants || _load_constants()).COMPONENT_DIR}}`)) {
        var resolvedConfigDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: rootDir, ENV_TYPE: '' });
        var allEnvFilesPaths = compilerFilesPaths.concat(testerFilesPaths);
        allEnvFilesPaths.forEach(function (file) {
          var ignoreFile = (0, (_utils || _load_utils()).pathJoinLinux)(resolvedConfigDir, file);
          ignoreList.files.push(ignoreFile);
        });
        var configDirWithoutCompDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: '', ENV_TYPE: '{ENV_TYPE}' });
        // There is nested folders to ignore
        if (configDirWithoutCompDir !== '' && configDirWithoutCompDir !== '/') {
          var configDirWithoutCompAndEnvsDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: '', ENV_TYPE: '' });
          // There is nested folder which is not the env folders - ignore it completely
          if (configDirWithoutCompAndEnvsDir !== '' && configDirWithoutCompAndEnvsDir !== '/') {
            var resolvedDirWithoutEnvType = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: rootDir, ENV_TYPE: '' });
            ignoreList.dirs.push((0, (_utils || _load_utils()).stripTrailingChar)(resolvedDirWithoutEnvType, '/'));
          } else {
            var resolvedCompilerConfigDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, {
              [(_constants || _load_constants()).COMPONENT_DIR]: rootDir,
              ENV_TYPE: (_compilerExtension || _load_compilerExtension()).COMPILER_ENV_TYPE
            });
            var resolvedTesterConfigDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: rootDir, ENV_TYPE: (_testerExtension || _load_testerExtension()).TESTER_ENV_TYPE });
            ignoreList.dirs.push(resolvedCompilerConfigDir, resolvedTesterConfigDir);
          }
        }
      } else {
        // Ignore the whole dir since this dir is only for config files
        var dirToIgnore = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { ENV_TYPE: '' });
        ignoreList.dirs.push(dirToIgnore);
      }
      return ignoreList;
    }

    /**
     * this is a temporarily method until ConfigDir class is merged into master
     */

  }, {
    key: 'parseConfigDir',
    value: function parseConfigDir(configDir, rootDir) {
      var configDirResolved = {};
      configDirResolved.compiler = configDir.getResolved({
        componentDir: rootDir,
        envType: (_compilerExtension || _load_compilerExtension()).COMPILER_ENV_TYPE
      }).linuxDirPath;
      configDirResolved.tester = configDir.getResolved({ componentDir: rootDir, envType: (_testerExtension || _load_testerExtension()).TESTER_ENV_TYPE }).linuxDirPath;
      return configDirResolved;
    }
  }]);

  return BitMap;
}();

exports.default = BitMap;