"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

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

  _path = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function _decamelize() {
  const data = _interopRequireDefault(require("decamelize"));

  _decamelize = function () {
    return data;
  };

  return data;
}

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

  _ramda = function () {
    return data;
  };

  return data;
}

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

  _exceptions = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

function _isValidIdChunk() {
  const data = _interopRequireDefault(require("../utils/is-valid-id-chunk"));

  _isValidIdChunk = function () {
    return data;
  };

  return data;
}

function _isValidScopeName() {
  const data = _interopRequireDefault(require("../utils/is-valid-scope-name"));

  _isValidScopeName = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

function _versionParser() {
  const data = _interopRequireDefault(require("../version/version-parser"));

  _versionParser = function () {
    return data;
  };

  return data;
}

class BitId {
  constructor({
    scope,
    box,
    name,
    version
  }) {
    // don't validate the id parts using isValidIdChunk here. we instance this class tons of times
    // and running regex so many times impact the performance
    if (!name) throw new (_exceptions().InvalidName)(name);
    this.scope = scope || null;
    this.box = null;
    this.name = box ? `${box}/${name}` : name;
    this.version = version || null;
    Object.freeze(this);
  }

  clone() {
    // $FlowFixMe
    return new BitId(this);
  }

  changeScope(newScope) {
    return new BitId({
      scope: newScope,
      name: this.name,
      version: this.version
    });
  }

  changeVersion(newVersion) {
    return new BitId({
      scope: this.scope,
      name: this.name,
      version: newVersion
    });
  }

  isLocal(scopeName) {
    return !this.scope || Boolean(scopeName && scopeName === this.scope);
  }

  getVersion() {
    return (0, _versionParser().default)(this.version);
  }

  hasVersion() {
    return Boolean(this.version && this.version !== _constants().LATEST_BIT_VERSION);
  }

  hasScope() {
    return Boolean(this.scope);
  }

  hasSameName(id) {
    return this.name === id.name;
  }

  hasSameScope(id) {
    if (this.hasScope() && id.hasScope()) return this.scope === id.scope;
    if (!this.hasScope() && !id.hasScope()) return true;
    return false; // one has scope but not the other
  }

  hasSameVersion(id) {
    if (this.hasVersion() && id.hasVersion()) return this.version === id.version;
    if (!this.hasVersion() && !id.hasVersion()) return true;
    return false; // one has version but not the other
  }

  toString(ignoreScope = false, ignoreVersion = false) {
    const {
      name,
      version
    } = this;
    const scope = this.scope;
    const componentStr = ignoreScope || !scope ? name : [scope, name].join('/'); // when there is no scope and the version is latest, omit the version.

    if (ignoreVersion || !this.hasVersion()) return componentStr; // $FlowFixMe version here is a string because this.hasVersion() is true

    return componentStr.concat(`${_constants().VERSION_DELIMITER}${version}`);
  }

  toStringWithoutScope() {
    return this.toString(true);
  }

  toStringWithoutVersion() {
    return this.toString(false, true);
  }

  toStringWithoutScopeAndVersion() {
    return this.toString(true, true);
  }

  isEqual(bitId) {
    return this.hasSameName(bitId) && this.hasSameScope(bitId) && this.hasSameVersion(bitId);
  }

  isEqualWithoutVersion(bitId) {
    return this.hasSameName(bitId) && this.hasSameScope(bitId);
  }

  isEqualWithoutScopeAndVersion(bitId) {
    return this.hasSameName(bitId);
  }

  serialize() {
    const obj = {
      scope: this.scope,
      name: this.name,
      version: this.version
    };
    if (!this.hasVersion()) delete obj.version;
    if (!this.hasScope()) delete obj.scope;
    return obj;
  }

  toObject() {
    const key = this.scope ? [this.scope, this.name].join('/') : this.name;
    const value = this.version;
    return {
      [key]: value
    };
  }

  toFullPath() {
    if (!this.scope || !this.version) {
      throw new Error('BitId.toFullPath is unable to generate a path without a scope or a version');
    }

    return _path().default.join(this.name, this.scope, this.version);
  }
  /**
   * Get a string id and return a string without the version part
   * @param {string} id
   * @return {string} id - id without version
   */


  static getStringWithoutVersion(id) {
    return id.split(_constants().VERSION_DELIMITER)[0];
  }

  static getVersionOnlyFromString(id) {
    return id.split(_constants().VERSION_DELIMITER)[1];
  }

  static parse(id, hasScope = true, version = _constants().LATEST_BIT_VERSION) {
    if (!_ramda().default.is(String, id)) {
      throw new TypeError(`BitId.parse expects to get "id" as a string, instead, got ${typeof id}`);
    }

    if (id.includes(_constants().VERSION_DELIMITER)) {
      const [newId, newVersion] = id.split(_constants().VERSION_DELIMITER);
      id = newId;
      version = newVersion;
    }

    const getScopeAndName = () => {
      if (hasScope) {
        const delimiterIndex = id.indexOf('/');
        if (delimiterIndex < 0) throw new (_exceptions().InvalidBitId)();
        const scope = id.substring(0, delimiterIndex);
        const name = id.substring(delimiterIndex + 1);
        return {
          scope,
          name
        };
      }

      return {
        scope: null,
        name: id
      };
    };

    const {
      scope,
      name
    } = getScopeAndName();
    if (!(0, _isValidIdChunk().default)(name)) throw new (_exceptions().InvalidName)(name);
    if (scope && !(0, _isValidScopeName().default)(scope)) throw new (_exceptions().InvalidScopeName)(scope);
    return new BitId({
      scope,
      name,
      version
    });
  }

  static parseObsolete(id, version = _constants().LATEST_BIT_VERSION) {
    if (id.includes(_constants().VERSION_DELIMITER)) {
      const [newId, newVersion] = id.split(_constants().VERSION_DELIMITER);
      id = newId;
      version = newVersion;
    }

    const idSplit = id.split('/');

    if (idSplit.length === 3) {
      const [scope, box, name] = idSplit;

      if (!(0, _isValidIdChunk().default)(name, false) || !(0, _isValidIdChunk().default)(box, false) || !(0, _isValidScopeName().default)(scope)) {
        // $FlowFixMe
        throw new (_exceptions().InvalidIdChunk)(`${scope}/${box}/${name}`);
      } // $FlowFixMe (in this case the realScopeName is not null)


      return new BitId({
        scope,
        box,
        name,
        version
      });
    }

    if (idSplit.length === 2) {
      const [box, name] = idSplit;

      if (!(0, _isValidIdChunk().default)(name, false) || !(0, _isValidIdChunk().default)(box, false)) {
        // $FlowFixMe
        throw new (_exceptions().InvalidIdChunk)(`${box}/${name}`);
      } // $FlowFixMe


      return new BitId({
        box,
        name,
        version
      });
    }

    if (idSplit.length === 1) {
      const [name] = idSplit;

      if (!(0, _isValidIdChunk().default)(name)) {
        // $FlowFixMe
        throw new (_exceptions().InvalidIdChunk)(name);
      } // $FlowFixMe


      return new BitId({
        name,
        version
      });
    }

    throw new (_exceptions().InvalidBitId)();
  }
  /**
   * before version 13.0.3 bitmap and component-dependencies ids were written as strings (e.g. scope/box/name@version)
   * since that version the ids are written as objects ({ scope: scopeName, name: compName, version: 0.0.1 })
   */


  static parseBackwardCompatible(id) {
    return typeof id === 'string' ? BitId.parseObsolete(id) : new BitId(id);
  }

  static getValidScopeName(scope) {
    const suggestedName = scope.toLowerCase();
    let cleanName = suggestedName.split('').map(char => {
      if (/^[$\-_!.a-z0-9]+$/.test(char)) return char;
      return '';
    }).join(''); // allow only one dot

    const nameSplitByDot = cleanName.split('.');

    if (nameSplitByDot.length > 1) {
      cleanName = `${_ramda().default.head(nameSplitByDot)}.${_ramda().default.tail(nameSplitByDot).join('')}`;
    }

    if (!cleanName) {
      throw new (_generalError().default)('scope name created by directory name have to contains at least one character or number');
    }

    return cleanName;
  }

  static getValidIdChunk(chunk) {
    if (!(0, _isValidIdChunk().default)(chunk)) {
      chunk = chunk.replace(/\./g, '');
      chunk = (0, _decamelize().default)(chunk, '-');
    }

    return chunk;
  }

  static getValidBitId(box, name) {
    return new BitId({
      name: BitId.getValidIdChunk(name),
      box: box ? BitId.getValidIdChunk(box) : undefined
    });
  }

  static isValidVersion(version) {
    return _semver().default.valid(version);
  }

}

exports.default = BitId;