"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 _stringFormat() {
  const data = _interopRequireDefault(require("string-format"));

  _stringFormat = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

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

  _ramda = function () {
    return data;
  };

  return data;
}

function _commentJson() {
  const data = _interopRequireDefault(require("comment-json"));

  _commentJson = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

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

  _exceptions = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

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

  _utils = function () {
    return data;
  };

  return data;
}

function _componentMap() {
  const data = _interopRequireDefault(require("./component-map"));

  _componentMap = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

function _invalidConfigDir() {
  const data = _interopRequireDefault(require("./exceptions/invalid-config-dir"));

  _invalidConfigDir = function () {
    return data;
  };

  return data;
}

function _config() {
  const data = _interopRequireDefault(require("../config"));

  _config = function () {
    return data;
  };

  return data;
}

function _configDir() {
  const data = _interopRequireDefault(require("./config-dir"));

  _configDir = function () {
    return data;
  };

  return data;
}

function _workspaceConfig() {
  const data = _interopRequireDefault(require("../config/workspace-config"));

  _workspaceConfig = function () {
    return data;
  };

  return data;
}

class BitMap {
  // path => componentId
  // path => componentId
  constructor(projectRoot, mapPath, version) {
    (0, _defineProperty2().default)(this, "_invalidateCache", () => {
      this.paths = {};
      this.pathsLowerCase = {};
      this._cacheIds = {};
      this.allTrackDirs = undefined;
    });
    this.projectRoot = projectRoot;
    this.mapPath = mapPath;
    this.components = {};
    this.hasChanged = false;
    this.version = version;
    this.paths = {};
    this.pathsLowerCase = {};
    this._cacheIds = {};
    this.markAsChangedBinded = this.markAsChanged.bind(this);
  }

  markAsChanged() {
    this.hasChanged = true;

    this._invalidateCache();
  }

  setComponent(bitId, componentMap) {
    const id = bitId.toString();

    if (!bitId.hasVersion() && bitId.scope) {
      throw new (_generalError().default)(`invalid bitmap id ${id}, a component must have a version when a scope-name is included`);
    }

    if (componentMap.origin !== _constants().COMPONENT_ORIGINS.NESTED) {
      // make sure there are no duplications (same name)
      const similarIds = this.findSimilarIds(bitId, true);

      if (similarIds.length) {
        throw new (_generalError().default)(`your id ${id} is duplicated with ${similarIds.toString()}`);
      }
    }

    componentMap.id = bitId;
    this.components[id] = componentMap;
    this.markAsChanged();
  }

  setComponentProp(id, propName, val) {
    const componentMap = this.getComponent(id, {
      ignoreScopeAndVersion: true
    }); // $FlowFixMe

    componentMap[propName] = val;
    this.markAsChanged();
    return componentMap;
  }

  isEmpty() {
    return _ramda().default.isEmpty(this.components);
  }

  removeComponentProp(id, propName) {
    const componentMap = this.getComponent(id, {
      ignoreScopeAndVersion: true
    }); // $FlowFixMe

    delete componentMap[propName];
    this.markAsChanged();
    return componentMap;
  }

  static load(dirPath) {
    const {
      currentLocation,
      defaultLocation
    } = BitMap.getBitMapLocation(dirPath);
    const mapFileContent = BitMap.loadRawSync(dirPath);

    if (!mapFileContent || !currentLocation) {
      return new BitMap(dirPath, defaultLocation, _constants().BIT_VERSION);
    }

    let componentsJson;

    try {
      componentsJson = _commentJson().default.parse(mapFileContent.toString('utf8'), null, true);
    } catch (e) {
      _logger().default.error(e);

      throw new (_exceptions().InvalidBitMap)(currentLocation, e.message);
    }

    const version = componentsJson.version; // Don't treat version like component

    delete componentsJson.version;
    const bitMap = new BitMap(dirPath, currentLocation, version);
    bitMap.loadComponents(componentsJson);
    return bitMap;
  }

  static loadRawSync(dirPath) {
    const {
      currentLocation
    } = BitMap.getBitMapLocation(dirPath);

    if (!currentLocation) {
      _logger().default.info(`bit.map: unable to find an existing ${_constants().BIT_MAP} file. Will create a new one if needed`);

      return undefined;
    }

    const mapFileContent = _fsExtra().default.readFileSync(currentLocation);

    return mapFileContent;
  }

  static getBitMapLocation(dirPath) {
    const defaultLocation = _path().default.join(dirPath, _constants().BIT_MAP);

    const oldLocation = _path().default.join(dirPath, _constants().OLD_BIT_MAP);

    const getCurrentLocation = () => {
      if (_fsExtra().default.existsSync(defaultLocation)) return defaultLocation;
      if (_fsExtra().default.existsSync(oldLocation)) return oldLocation;
      return null;
    };

    const currentLocation = getCurrentLocation();
    return {
      currentLocation,
      defaultLocation
    };
  }
  /**
   * if resetHard, delete the bitMap file.
   * Otherwise, try to load it and only if the file is corrupted then delete it.
   */


  static reset(dirPath, resetHard) {
    const bitMapPath = _path().default.join(dirPath, _constants().BIT_MAP);

    const deleteBitMapFile = () => {
      _logger().default.info(`deleting the bitMap file at ${bitMapPath}`);

      _fsExtra().default.removeSync(bitMapPath);
    };

    if (resetHard) {
      deleteBitMapFile();
      return;
    }

    try {
      BitMap.load(dirPath);
    } catch (err) {
      if (err instanceof _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
   */


  static resolveIgnoreFilesAndDirs(configDir, rootDir, compilerFilesPaths = [], testerFilesPaths = []) {
    const ignoreList = {
      files: [],
      dirs: []
    };
    if (!configDir) return ignoreList;

    if (configDir.startsWith(`{${_constants().COMPONENT_DIR}}`)) {
      const resolvedConfigDir = (0, _stringFormat().default)(configDir, {
        [_constants().COMPONENT_DIR]: rootDir,
        ENV_TYPE: ''
      });
      const allEnvFilesPaths = compilerFilesPaths.concat(testerFilesPaths);
      allEnvFilesPaths.forEach(file => {
        const ignoreFile = (0, _utils().pathJoinLinux)(resolvedConfigDir, file);
        ignoreList.files.push(ignoreFile);
      });
      const configDirWithoutCompDir = (0, _stringFormat().default)(configDir, {
        [_constants().COMPONENT_DIR]: '',
        ENV_TYPE: '{ENV_TYPE}'
      }); // There is nested folders to ignore

      if (configDirWithoutCompDir !== '' && configDirWithoutCompDir !== '/') {
        const configDirWithoutCompAndEnvsDir = (0, _stringFormat().default)(configDir, {
          [_constants().COMPONENT_DIR]: '',
          ENV_TYPE: ''
        }); // There is nested folder which is not the env folders - ignore it completely

        if (configDirWithoutCompAndEnvsDir !== '' && configDirWithoutCompAndEnvsDir !== '/') {
          const resolvedDirWithoutEnvType = (0, _stringFormat().default)(configDir, {
            [_constants().COMPONENT_DIR]: rootDir,
            ENV_TYPE: ''
          });
          ignoreList.dirs.push((0, _utils().stripTrailingChar)(resolvedDirWithoutEnvType, '/'));
        } else {
          const resolvedCompilerConfigDir = (0, _stringFormat().default)(configDir, {
            [_constants().COMPONENT_DIR]: rootDir,
            ENV_TYPE: _constants().COMPILER_ENV_TYPE
          });
          const resolvedTesterConfigDir = (0, _stringFormat().default)(configDir, {
            [_constants().COMPONENT_DIR]: rootDir,
            ENV_TYPE: _constants().TESTER_ENV_TYPE
          });
          ignoreList.dirs.push(resolvedCompilerConfigDir, resolvedTesterConfigDir);
        }
      }
    } else {
      // Ignore the whole dir since this dir is only for config files
      const dirToIgnore = (0, _stringFormat().default)(configDir, {
        ENV_TYPE: ''
      });
      ignoreList.dirs.push(dirToIgnore);
    }

    return ignoreList;
  }
  /**
   * this is a temporarily method until ConfigDir class is merged into master
   */


  static parseConfigDir(configDir, rootDir) {
    const configDirResolved = {};
    configDirResolved.compiler = configDir.getResolved({
      componentDir: rootDir,
      envType: _constants().COMPILER_ENV_TYPE
    }).linuxDirPath;
    configDirResolved.tester = configDir.getResolved({
      componentDir: rootDir,
      envType: _constants().TESTER_ENV_TYPE
    }).linuxDirPath;
    return configDirResolved;
  }

  loadComponents(componentsJson) {
    Object.keys(componentsJson).forEach(componentId => {
      const componentFromJson = componentsJson[componentId];

      const idHasScope = () => {
        if (componentFromJson.origin !== _constants().COMPONENT_ORIGINS.AUTHORED) return true;

        if ('exported' in componentFromJson) {
          return componentFromJson.exported;
        } // backward compatibility


        return _bitId().BitId.parseObsolete(componentId).hasScope();
      };

      componentFromJson.id = _bitId().BitId.parse(componentId, idHasScope());

      const componentMap = _componentMap().default.fromJson(componentsJson[componentId]);

      componentMap.setMarkAsChangedCb(this.markAsChangedBinded);
      this.components[componentId] = componentMap;
    });
  }

  getAllComponents(origin) {
    if (!origin) return this.components;

    const isOriginMatch = component => component.origin === origin; // $FlowFixMe we know origin is an array in that case


    const isOriginMatchArray = component => origin.includes(component.origin);

    const filter = Array.isArray(origin) ? isOriginMatchArray : isOriginMatch;
    return _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
   */


  getConfigDirsAndFilesToIgnore(consumerPath, workspaceConfig) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const ignoreList = {
        files: [],
        dirs: []
      };

      const populateIgnoreListP = _ramda().default.values(_this.components).map(
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* (component) {
          const configDir = component.configDir;
          const componentDir = component.getComponentDir();

          if (configDir && componentDir) {
            const resolvedBaseConfigDir = component.getBaseConfigDir() || '';

            const fullConfigDir = _path().default.join(consumerPath, resolvedBaseConfigDir);

            const componentConfig = yield _config().default.load({
              componentDir: component.rootDir,
              workspaceDir: consumerPath,
              configDir: fullConfigDir,
              workspaceConfig
            });

            const compilerObj = _ramda().default.values(componentConfig.compiler)[0];

            const compilerFilesObj = compilerObj && compilerObj.files ? compilerObj.files : undefined;

            const testerObj = _ramda().default.values(componentConfig.tester)[0];

            const testerFilesObj = testerObj && testerObj.files ? testerObj.files : undefined;
            const compilerFiles = compilerFilesObj ? _ramda().default.values(compilerFilesObj) : [];
            const testerFiles = testerFilesObj ? _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

            const 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 function (_x) {
          return _ref.apply(this, arguments);
        };
      }());

      yield Promise.all(populateIgnoreListP);
      return ignoreList;
    })();
  }

  getAllBitIds(origin) {
    const ids = componentMaps => _bitId().BitIds.fromArray(componentMaps.map(c => c.id));

    const getIdsOfOrigin = oneOrigin => {
      const cacheKey = oneOrigin || 'all';
      if (this._cacheIds[cacheKey]) return this._cacheIds[cacheKey];

      const allComponents = _ramda().default.values(this.components);

      const components = oneOrigin ? allComponents.filter(c => c.origin === oneOrigin) : allComponents;
      const componentIds = ids(components);
      this._cacheIds[cacheKey] = componentIds;
      return componentIds;
    };

    if (!origin) return getIdsOfOrigin();
    return _bitId().BitIds.fromArray(_ramda().default.flatten(origin.map(oneOrigin => getIdsOfOrigin(oneOrigin))));
  }
  /**
   * get existing bitmap bit-id by bit-id.
   * throw an exception if not found
   * @see also getBitIdIfExist
   */


  getBitId(bitId, {
    ignoreVersion = false,
    ignoreScopeAndVersion = false
  } = {}) {
    if (!(bitId instanceof _bitId().BitId)) {
      throw new TypeError(`BitMap.getBitId expects bitId to be an instance of BitId, instead, got ${bitId}`);
    }

    const allIds = this.getAllBitIds();
    const exactMatch = allIds.search(bitId);
    if (exactMatch) return exactMatch;

    if (ignoreVersion) {
      const matchWithoutVersion = allIds.searchWithoutVersion(bitId);
      if (matchWithoutVersion) return matchWithoutVersion;
    }

    if (ignoreScopeAndVersion) {
      const matchWithoutScopeAndVersion = allIds.searchWithoutScopeAndVersion(bitId);
      if (matchWithoutScopeAndVersion) return matchWithoutScopeAndVersion;
    }

    throw new (_exceptions().MissingBitMapComponent)(bitId.toString());
  }
  /**
   * get existing bitmap bit-id by bit-id
   * don't throw an exception if not found
   * @see also getBitId
   */


  getBitIdIfExist(bitId, {
    ignoreVersion = false,
    ignoreScopeAndVersion = false
  } = {}) {
    try {
      const existingBitId = this.getBitId(bitId, {
        ignoreVersion,
        ignoreScopeAndVersion
      });
      return existingBitId;
    } catch (err) {
      if (err instanceof _exceptions().MissingBitMapComponent) return null;
      throw err;
    }
  }
  /**
   * get componentMap from bitmap by bit-id.
   * throw an exception if not found.
   * @see also getComponentIfExist
   */


  getComponent(bitId, {
    ignoreVersion = false,
    ignoreScopeAndVersion = false
  } = {}) {
    const 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
   */


  getComponentIfExist(bitId, {
    ignoreVersion = false,
    ignoreScopeAndVersion = false
  } = {}) {
    try {
      const componentMap = this.getComponent(bitId, {
        ignoreVersion,
        ignoreScopeAndVersion
      });
      return componentMap;
    } catch (err) {
      if (err instanceof _exceptions().MissingBitMapComponent) return null;
      throw err;
    }
  }

  getNonNestedComponentIfExist(bitId) {
    const nonNestedIds = this.getAllBitIds([_constants().COMPONENT_ORIGINS.IMPORTED, _constants().COMPONENT_ORIGINS.AUTHORED]);
    const id = nonNestedIds.searchWithoutScopeAndVersion(bitId);
    if (!id) return null;
    return this.getComponent(id);
  }

  getComponentPreferNonNested(bitId) {
    return this.getNonNestedComponentIfExist(bitId) || this.getComponentIfExist(bitId, {
      ignoreVersion: true
    });
  }

  getAuthoredAndImportedBitIds() {
    return this.getAllBitIds([_constants().COMPONENT_ORIGINS.AUTHORED, _constants().COMPONENT_ORIGINS.IMPORTED]);
  }

  getAuthoredExportedComponents() {
    const authoredIds = this.getAllBitIds([_constants().COMPONENT_ORIGINS.AUTHORED]);
    return authoredIds.filter(id => id.hasScope());
  }

  validateConfigDir(compId, configDir) {
    const components = this.getAllComponents();

    if (configDir.startsWith('./')) {
      configDir = configDir.replace('./', '');
    }

    const comps = _ramda().default.pickBy(component => {
      const compDir = component.getComponentDir();

      if (compDir && (0, _utils().pathIsInside)(configDir, compDir)) {
        return true;
      }

      const compConfigDir = component.configDir && component.configDir instanceof _configDir().default ? component.configDir.getResolved({
        componentDir: compDir || ''
      }).getEnvTypeCleaned().linuxDirPath : null;

      if (compConfigDir && (0, _utils().pathIsInside)(configDir, compConfigDir)) {
        return true;
      }

      return false;
    }, components);

    if (!_ramda().default.isEmpty(comps)) {
      const id = _ramda().default.keys(comps)[0];

      const stringId = _bitId().BitId.parse(id).toStringWithoutVersion();

      if (compId !== stringId) {
        throw new (_invalidConfigDir().default)(stringId);
      }
    }

    return true;
  }

  _makePathRelativeToProjectRoot(pathToChange) {
    const absolutePath = _path().default.resolve(pathToChange);

    return _path().default.relative(this.projectRoot, absolutePath);
  }
  /**
   * find ids that have the same name but different version
   * if compareWithoutScope is false, the scope should be identical in addition to the name
   */


  findSimilarIds(id, compareWithoutScope = false) {
    const allIds = this.getAllBitIds([_constants().COMPONENT_ORIGINS.IMPORTED, _constants().COMPONENT_ORIGINS.AUTHORED]);
    const similarIds = allIds.filter(existingId => {
      const isSimilar = compareWithoutScope ? existingId.isEqualWithoutScopeAndVersion(id) : existingId.isEqualWithoutVersion(id);
      return isSimilar && !existingId.isEqual(id);
    });
    return _bitId().BitIds.fromArray(similarIds);
  }

  deleteOlderVersionsOfComponent(componentId) {
    const similarIds = this.findSimilarIds(componentId);
    similarIds.forEach(id => {
      const idStr = id.toString();

      _logger().default.debugAndAddBreadCrumb('BitMap.deleteOlderVersionsOfComponent', 'deleting an older version {idStr} of an existing component {componentId}', {
        idStr,
        componentId: componentId.toString()
      });

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


  getExistingBitId(id, shouldThrow = true) {
    if (!_ramda().default.is(String, id)) {
      throw new TypeError(`BitMap.getExistingBitId expects id to be a string, instead, got ${typeof id}`);
    }

    const components = _ramda().default.values(this.components);

    const idHasVersion = id.includes(_constants().VERSION_DELIMITER); // start with a more strict comparison. assume the id from the user has a scope name

    const componentWithScope = components.find(componentMap => {
      return idHasVersion ? componentMap.id.toString() === id : componentMap.id.toStringWithoutVersion() === id;
    });
    if (componentWithScope) return componentWithScope.id; // continue with searching without the scope name

    const idWithoutVersion = _bitId().BitId.getStringWithoutVersion(id);

    const componentWithoutScope = components.find(componentMap => {
      return idHasVersion ? componentMap.id.toStringWithoutScope() === id : componentMap.id.toStringWithoutScopeAndVersion() === idWithoutVersion;
    });
    if (componentWithoutScope) return componentWithoutScope.id;

    if (shouldThrow) {
      throw new (_exceptions().MissingBitMapComponent)(id);
    }

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


  _areFilesArraysEqual(filesA, filesB) {
    if (filesA.length !== filesB.length) return false;

    const cmp = (x, y) => x.relativePath === y.relativePath;

    const diff = _ramda().default.differenceWith(cmp, filesA, filesB);

    if (!diff.length) return true;
    return false;
  }
  /**
   * add files from filesB that are not in filesA
   */


  mergeFilesArray(filesA, filesB) {
    return _ramda().default.unionWith(_ramda().default.eqBy(_ramda().default.prop('relativePath')), filesA, filesB);
  }

  addComponent({
    componentId,
    files,
    mainFile,
    origin,
    rootDir,
    configDir,
    trackDir,
    originallySharedDir,
    wrapDir
  }) {
    const componentIdStr = componentId.toString();

    _logger().default.debug(`adding to bit.map ${componentIdStr}`);

    if (this.components[componentIdStr]) {
      _logger().default.info(`bit.map: updating an exiting component ${componentIdStr}`);

      this.components[componentIdStr].files = files;
    } else {
      if (origin === _constants().COMPONENT_ORIGINS.IMPORTED || origin === _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


      const componentMap = new (_componentMap().default)({
        files,
        origin
      });
      componentMap.setMarkAsChangedCb(this.markAsChangedBinded);
      this.setComponent(componentId, componentMap);
    }

    this.components[componentIdStr].mainFile = mainFile;

    if (rootDir) {
      this.components[componentIdStr].rootDir = (0, _utils().pathNormalizeToLinux)(rootDir);
    }

    if (configDir) {
      this.components[componentIdStr].configDir = configDir;
    }

    if (trackDir) {
      this.components[componentIdStr].trackDir = (0, _utils().pathNormalizeToLinux)(trackDir);
    }

    if (wrapDir) {
      this.components[componentIdStr].wrapDir = wrapDir;
    }

    this.components[componentIdStr].removeTrackDirIfNeeded();

    if (originallySharedDir) {
      this.components[componentIdStr].originallySharedDir = originallySharedDir;
    }

    this.sortValidateAndMarkAsChanged(componentIdStr);
    return this.components[componentIdStr];
  }

  addFilesToComponent({
    componentId,
    files
  }) {
    const componentIdStr = componentId.toString();

    if (!this.components[componentIdStr]) {
      throw new (_generalError().default)(`unable to add files to a non-exist component ${componentIdStr}`);
    }

    _logger().default.info(`bit.map: updating an exiting component ${componentIdStr}`);

    this.components[componentIdStr].files = files;
    this.sortValidateAndMarkAsChanged(componentIdStr);
    return this.components[componentIdStr];
  }

  sortValidateAndMarkAsChanged(componentIdStr) {
    this.components[componentIdStr].sort();
    this.components[componentIdStr].validate();
    this.markAsChanged();
  }

  _removeFromComponentsArray(componentId) {
    delete this.components[componentId.toString()];
    this.markAsChanged();
  }

  removeComponent(bitId) {
    const bitmapComponent = this.getBitIdIfExist(bitId, {
      ignoreScopeAndVersion: true
    });
    if (bitmapComponent) this._removeFromComponentsArray(bitmapComponent);
    return bitmapComponent;
  }

  removeComponents(ids) {
    return ids.map(id => this.removeComponent(id));
  }

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


  updateComponentId(id, updateScopeOnly = false) {
    const newIdString = id.toString();
    const similarIds = this.findSimilarIds(id, true);

    if (!similarIds.length) {
      _logger().default.debug(`bit-map: no need to update ${newIdString}`);

      return id;
    }

    if (similarIds.length > 1) {
      throw new (_generalError().default)(`Your ${_constants().BIT_MAP} file has more than one version of ${id.toStringWithoutScopeAndVersion()} and they
      are authored or imported. This scenario is not supported`);
    }

    const oldId = similarIds[0];
    const oldIdStr = oldId.toString();
    const newId = updateScopeOnly ? oldId.changeScope(id.scope) : id;

    if (newId.isEqual(oldId)) {
      _logger().default.debug(`bit-map: no need to update ${oldIdStr}`);

      return oldId;
    }

    _logger().default.debug(`BitMap: updating an older component ${oldIdStr} with a newer component ${newId.toString()}`);

    const componentMap = this.components[oldIdStr];

    if (componentMap.origin === _constants().COMPONENT_ORIGINS.NESTED) {
      throw new Error('updateComponentId should not manipulate Nested components');
    }

    this._removeFromComponentsArray(oldId);

    this.setComponent(newId, componentMap);
    this.markAsChanged();
    return newId;
  }
  /**
   * 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
   */


  getComponentObjectOfFileByPath(componentPath) {
    const components = this.getAllComponents();
    return _ramda().default.pickBy(component => (0, _utils().pathIsInside)(componentPath, component.rootDir || this.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
   */


  getComponentIdByPath(componentPath, caseSensitive = true) {
    this._populateAllPaths();

    return caseSensitive ? this.paths[componentPath] : this.pathsLowerCase[componentPath.toLowerCase()];
  }

  _populateAllPaths() {
    if (_ramda().default.isEmpty(this.paths)) {
      Object.keys(this.components).forEach(componentId => {
        const component = this.components[componentId];
        component.files.forEach(file => {
          const relativeToConsumer = component.rootDir ? (0, _utils().pathJoinLinux)(component.rootDir, file.relativePath) : file.relativePath;
          this.paths[relativeToConsumer] = component.id;
          this.pathsLowerCase[relativeToConsumer.toLowerCase()] = component.id;
        });
      });
    }
  }

  getAllTrackDirs() {
    if (!this.allTrackDirs) {
      this.allTrackDirs = {};
      Object.keys(this.components).forEach(componentId => {
        const component = this.components[componentId];
        if (!component.trackDir) return; // $FlowFixMe

        this.allTrackDirs[component.trackDir] = component.id;
      });
    }

    return this.allTrackDirs;
  }

  updatePathLocation(from, to, existingPath) {
    const isPathDir = (0, _utils().isDir)(existingPath);
    const allChanges = [];
    Object.keys(this.components).forEach(componentId => {
      const componentMap = this.components[componentId];
      const changes = isPathDir ? componentMap.updateDirLocation(from, to) : componentMap.updateFileLocation(from, to);
      if (changes && changes.length) allChanges.push({
        id: componentMap.id.clone(),
        changes
      });
    });

    if (_ramda().default.isEmpty(allChanges)) {
      const errorMsg = isPathDir ? `directory ${from} is not a tracked component` : `the file ${existingPath} is untracked`;
      throw new (_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
   */


  toObjects() {
    const components = {};
    Object.keys(this.components).forEach(id => {
      const componentMap = this.components[id].clone();

      if (componentMap.origin === _constants().COMPONENT_ORIGINS.AUTHORED) {
        componentMap.exported = componentMap.id.hasScope();
      }

      delete componentMap.id;
      components[id] = componentMap.toPlainObject();
    });
    return (0, _utils().sortObject)(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
   */


  write() {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!_this2.hasChanged) return null;

      _logger().default.debug('writing to bit.map');

      const bitMapContent = _this2.getContent();

      return (0, _utils().outputFile)({
        filePath: _this2.mapPath,
        content: JSON.stringify(bitMapContent, null, 4)
      });
    })();
  }

  getContent() {
    const bitMapContent = Object.assign({}, this.toObjects(), {
      version: this.version
    });
    return bitMapContent;
  }

}

exports.default = BitMap;