"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 _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

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

  _ramda = function () {
    return data;
  };

  return data;
}

function _objects() {
  const data = require("../objects");

  _objects = function () {
    return data;
  };

  return data;
}

function _scopeMeta() {
  const data = _interopRequireDefault(require("./scopeMeta"));

  _scopeMeta = function () {
    return data;
  };

  return data;
}

function _source() {
  const data = _interopRequireDefault(require("./source"));

  _source = function () {
    return data;
  };

  return data;
}

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

  _exceptions = 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 _bitId() {
  const data = _interopRequireDefault(require("../../bit-id/bit-id"));

  _bitId = function () {
    return data;
  };

  return data;
}

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

  _component = function () {
    return data;
  };

  return data;
}

function _componentVersion() {
  const data = _interopRequireDefault(require("../component-version"));

  _componentVersion = function () {
    return data;
  };

  return data;
}

function _sources() {
  const data = require("../../consumer/component/sources");

  _sources = function () {
    return data;
  };

  return data;
}

function _componentObjects() {
  const data = _interopRequireDefault(require("../component-objects"));

  _componentObjects = function () {
    return data;
  };

  return data;
}

function _specsResults() {
  const data = _interopRequireDefault(require("../../consumer/specs-results"));

  _specsResults = function () {
    return data;
  };

  return data;
}

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

  _logger = 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;
}

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

  _componentOverrides = function () {
    return data;
  };

  return data;
}

function _envFactory() {
  const data = require("../../extensions/env-factory");

  _envFactory = function () {
    return data;
  };

  return data;
}

const VERSION_ZERO = '0.0.0';
/**
 * we can't rename the class as ModelComponent because old components are already saved in the model
 * with 'Component' in their headers. see object-registrar.types()
 */

class Component extends _objects().BitObject {
  constructor(props) {
    super();
    if (!props.name) throw new TypeError('Model Component constructor expects to get a name parameter');
    this.scope = props.scope || null;
    this.name = props.name;
    this.versions = props.versions || {};
    this.lang = props.lang || _constants().DEFAULT_LANGUAGE;
    this.deprecated = props.deprecated || false;
    this.bindingPrefix = props.bindingPrefix || _constants().DEFAULT_BINDINGS_PREFIX;
    this.local = props.local;
    this.state = props.state || {};
  }

  get versionArray() {
    return (0, _utils().values)(this.versions);
  }

  listVersions(sort) {
    const versions = Object.keys(this.versions);
    if (!sort) return versions;

    if (sort === 'ASC') {
      return versions.sort(_semver().default.compare);
    }

    return versions.sort(_semver().default.compare).reverse();
  }

  hasVersion(version) {
    return Boolean(this.versions[version]);
  }
  /**
   * returns only the versions that exist in both components (regardless whether the hash are the same)
   * e.g. this.component = [0.0.1, 0.0.2, 0.0.3], other component = [0.0.3, 0.0.4]. it returns only [0.0.3].
   * also, in case it is coming from 'bit import', the version must be locally changed.
   * otherwise, it doesn't matter whether the hashes are different.
   */


  _getComparableVersionsObjects(otherComponent, // in case of merging, the otherComponent is the existing component, and "this" is the incoming component
  local) {
    const otherLocalVersion = otherComponent.getLocalVersions();
    const otherComponentVersions = (0, _utils().filterObject)(otherComponent.versions, (val, key) => Object.keys(this.versions).includes(key) && (!local || otherLocalVersion.includes(key)));
    const thisComponentVersions = (0, _utils().filterObject)(this.versions, (val, key) => Object.keys(otherComponentVersions).includes(key) && (!local || otherLocalVersion.includes(key)));
    return {
      thisComponentVersions,
      otherComponentVersions
    };
  }

  compatibleWith(component, local) {
    const {
      thisComponentVersions,
      otherComponentVersions
    } = this._getComparableVersionsObjects(component, local);

    return (0, _ramda().equals)(thisComponentVersions, otherComponentVersions);
  }

  diffWith(component, local) {
    const {
      thisComponentVersions,
      otherComponentVersions
    } = this._getComparableVersionsObjects(component, local);

    return Object.keys(thisComponentVersions).filter(version => thisComponentVersions[version].hash !== otherComponentVersions[version].hash);
  }

  latest() {
    if ((0, _utils().empty)(this.versions)) return VERSION_ZERO;
    return _semver().default.maxSatisfying(this.listVersions(), '*');
  }
  /**
   * Return the lateset version which actuall exists in the scope
   * (exists means the object itself exists)
   * This relevant for cases when the component version array has few versions
   * but we don't have all the refs in the object
   *
   * @returns {number}
   * @memberof Component
   */


  latestExisting(repository) {
    if ((0, _utils().empty)(this.versions)) return VERSION_ZERO;
    const versions = this.listVersions('ASC');
    let version = null;
    let versionStr = null;

    while (!version && versions && versions.length) {
      versionStr = versions.pop();
      version = this.loadVersionSync(versionStr, repository, false);
    }

    return versionStr || VERSION_ZERO;
  }

  collectLogs(repo) {
    return repo.findMany(this.versionArray).then(versions => {
      const indexedLogs = (0, _ramda().fromPairs)((0, _ramda().zip)((0, _ramda().keys)(this.versions), (0, _ramda().map)((0, _ramda().prop)('log'), versions)));
      return indexedLogs;
    });
  }
  /**
   * when bitMap is passed, it got called from the consumer and as such it strips the sharedDir
   */


  collectVersions(repo) {
    return Promise.all(this.listVersions().map(versionNum => {
      return this.toConsumerComponent(versionNum, this.scope, repo);
    }));
  }

  getVersionToAdd(releaseType = _constants().DEFAULT_BIT_RELEASE_TYPE, exactVersion) {
    if (exactVersion && this.versions[exactVersion]) {
      throw new (_exceptions().VersionAlreadyExists)(exactVersion, this.id());
    }

    return exactVersion || this.version(releaseType);
  }
  /**
   * if exactVersion is defined, add exact version instead of using the semver mechanism
   */


  addVersion(version, releaseType = _constants().DEFAULT_BIT_RELEASE_TYPE, exactVersion) {
    const versionToAdd = this.getVersionToAdd(releaseType, exactVersion);
    this.versions[versionToAdd] = version.hash();
    this.markVersionAsLocal(versionToAdd);
    return versionToAdd;
  }

  version(releaseType = _constants().DEFAULT_BIT_RELEASE_TYPE) {
    const latest = this.latest();
    if (latest) return _semver().default.inc(latest, releaseType);
    return _constants().DEFAULT_BIT_VERSION;
  }

  id() {
    return this.scope ? [this.scope, this.name].join('/') : this.name;
  }

  toBitId() {
    return new (_bitId().default)({
      scope: this.scope,
      name: this.name
    });
  }

  toBitIdWithLatestVersion() {
    return new (_bitId().default)({
      scope: this.scope,
      name: this.name,
      version: this.latest()
    });
  }

  toBitIdWithLatestVersionAllowNull() {
    const id = this.toBitIdWithLatestVersion();
    return id.version === VERSION_ZERO ? id.changeVersion(null) : id;
  }

  toObject() {
    function versions(vers) {
      const obj = {};
      (0, _utils().forEach)(vers, (ref, version) => {
        obj[version] = ref.toString();
      });
      return obj;
    }

    const componentObject = {
      name: this.name,
      scope: this.scope,
      versions: versions(this.versions),
      lang: this.lang,
      deprecated: this.deprecated,
      bindingPrefix: this.bindingPrefix
    };
    if (this.local) componentObject.local = this.local;
    if (!(0, _ramda().isEmpty)(this.state)) componentObject.state = this.state;
    return componentObject;
  }

  loadVersion(version, repository) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const versionRef = _this.versions[version];
      if (!versionRef) throw new (_exceptions().VersionNotFound)(version);
      return versionRef.load(repository);
    })();
  }

  loadVersionSync(version, repository, throws = true) {
    const versionRef = this.versions[version];
    if (!versionRef) throw new (_exceptions().VersionNotFound)(version);
    return versionRef.loadSync(repository, throws);
  }

  collectObjects(repo) {
    return Promise.all([this.asRaw(repo), this.collectRaw(repo)]).then(([rawComponent, objects]) => new (_componentObjects().default)(rawComponent, objects));
  }
  /**
   * to delete a version from a component, don't call this method directly. Instead, use sources.removeVersion()
   */


  removeVersion(version) {
    const objectRef = this.versions[version];
    delete this.versions[version];
    if (this.state.versions && this.state.versions[version]) delete this.state.versions[version];
    return objectRef;
  }

  toComponentVersion(versionStr) {
    const versionNum = (0, _versionParser().default)(versionStr).resolve(this.listVersions());

    if (!this.versions[versionNum]) {
      throw new (_generalError().default)(`the version ${versionNum} does not exist in ${this.listVersions().join('\n')}, versions array`);
    }

    return new (_componentVersion().default)(this, versionNum);
  }
  /**
   * convert a ModelComponent of a specific version to ConsumerComponent
   * when it's being called from the Consumer, some manipulation are done on the component, such
   * as stripping the originallySharedDir and adding wrapDir.
   * when it's being called from the Scope, no manipulations are done.
   *
   * @see sources.consumerComponentToVersion() for the opposite action.
   */


  toConsumerComponent(versionStr, scopeName, repository, manipulateDirData) {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug(`model-component, converting ${_this2.id()}, version: ${versionStr} to ConsumerComponent`);

      const componentVersion = _this2.toComponentVersion(versionStr);

      const version = yield componentVersion.getVersion(repository);

      const loadFileInstance = ClassName =>
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* (file) {
          const loadP = file.file.load(repository);
          const content = yield loadP;
          if (!content) throw new (_generalError().default)(`failed loading file ${file.relativePath} from the model`);
          return new ClassName({
            base: '.',
            path: file.relativePath,
            contents: content.contents,
            test: file.test
          });
        });

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

      const filesP = version.files ? Promise.all(version.files.map(loadFileInstance(_sources().SourceFile))) : null;
      const distsP = version.dists ? Promise.all(version.dists.map(loadFileInstance(_sources().Dist))) : null;
      const scopeMetaP = scopeName ? _scopeMeta().default.fromScopeName(scopeName).load(repository) : Promise.resolve();
      const log = version.log || null;
      const compilerP = (0, _envFactory().makeEnvFromModel)(_constants().COMPILER_ENV_TYPE, version.compiler, repository);
      const testerP = (0, _envFactory().makeEnvFromModel)(_constants().TESTER_ENV_TYPE, version.tester, repository);
      const [files, dists, scopeMeta, compiler, tester] = yield Promise.all([filesP, distsP, scopeMetaP, compilerP, testerP]);
      const bindingPrefix = _this2.bindingPrefix === 'bit' ? '@bit' : _this2.bindingPrefix; // when generating a new ConsumerComponent out of Version, it is critical to make sure that
      // all objects are cloned and not copied by reference. Otherwise, every time the
      // ConsumerComponent instance is changed, the Version will be changed as well, and since
      // the Version instance is saved in the Repository._cache, the next time a Version instance
      // is retrieved, it'll be different than the first time.

      const consumerComponent = new (_component().default)({
        name: _this2.name,
        version: componentVersion.version,
        scope: _this2.scope,
        lang: _this2.lang,
        bindingPrefix,
        mainFile: version.mainFile || null,
        compiler,
        tester,
        dependencies: version.dependencies.getClone(),
        devDependencies: version.devDependencies.getClone(),
        compilerDependencies: version.compilerDependencies.getClone(),
        testerDependencies: version.testerDependencies.getClone(),
        flattenedDependencies: version.flattenedDependencies.clone(),
        flattenedDevDependencies: version.flattenedDevDependencies.clone(),
        flattenedCompilerDependencies: version.flattenedCompilerDependencies.clone(),
        flattenedTesterDependencies: version.flattenedTesterDependencies.clone(),
        packageDependencies: (0, _ramda().clone)(version.packageDependencies),
        devPackageDependencies: (0, _ramda().clone)(version.devPackageDependencies),
        peerPackageDependencies: (0, _ramda().clone)(version.peerPackageDependencies),
        compilerPackageDependencies: (0, _ramda().clone)(version.compilerPackageDependencies),
        testerPackageDependencies: (0, _ramda().clone)(version.testerPackageDependencies),
        files,
        dists,
        mainDistFile: version.mainDistFile,
        docs: version.docs,
        license: scopeMeta ? _sources().License.deserialize(scopeMeta.license) : null,
        // todo: make sure we have license in case of local scope
        specsResults: version.specsResults ? version.specsResults.map(res => _specsResults().default.deserialize(res)) : null,
        log,
        customResolvedPaths: (0, _ramda().clone)(version.customResolvedPaths),
        overrides: _componentOverrides().default.loadFromScope(version.overrides),
        packageJsonChangedProps: (0, _ramda().clone)(version.packageJsonChangedProps),
        deprecated: _this2.deprecated
      });

      if (manipulateDirData) {
        consumerComponent.stripOriginallySharedDir(manipulateDirData);
        consumerComponent.addWrapperDir(manipulateDirData);
      }

      return consumerComponent;
    })();
  }

  refs() {
    return (0, _utils().values)(this.versions);
  }

  replaceRef(oldRef, newRef) {
    const replace = (value, key) => {
      if (value === oldRef.hash) {
        this.versions[key] = newRef.hash;
      }
    };

    (0, _ramda().forEachObjIndexed)(replace, this.versions);
  }

  validateBeforePersisting(componentStr) {
    _logger().default.debug(`validating component object: ${this.hash().hash} ${this.id()}`);

    const component = Component.parse(componentStr);
    component.validate();
  }

  toBuffer(pretty) {
    const args = (0, _utils().getStringifyArgs)(pretty);
    const obj = this.toObject();
    const str = JSON.stringify(obj, ...args);
    if (this.validateBeforePersist) this.validateBeforePersisting(str);
    return Buffer.from(str);
  }
  /**
   * Clear data that is relevant only for the local scope and should not be moved to the remote scope
   */


  clearStateData() {
    this.local = false; // backward compatibility for components created before 0.12.6

    this.state = {};
  }

  markVersionAsLocal(version) {
    if (!this.state.versions) this.state = {
      versions: {}
    }; // $FlowFixMe

    if (!this.state.versions[version]) this.state.versions[version] = {}; // $FlowFixMe

    this.state.versions[version].local = true;
  }

  getLocalVersions() {
    if ((0, _ramda().isEmpty)(this.state) || (0, _ramda().isEmpty)(this.state.versions)) return [];
    return Object.keys(this.state.versions).filter(version => this.state.versions[version].local);
  }

  isLocallyChanged() {
    if (this.local) return true; // backward compatibility for components created before 0.12.6

    if ((0, _ramda().isEmpty)(this.state) || (0, _ramda().isEmpty)(this.state.versions)) return false; // $FlowFixMe

    const localVersions = this.getLocalVersions();
    return localVersions.length > 0;
  }

  static parse(contents) {
    const rawComponent = JSON.parse(contents);
    return Component.from({
      name: rawComponent.box ? `${rawComponent.box}/${rawComponent.name}` : rawComponent.name,
      scope: rawComponent.scope,
      versions: (0, _utils().mapObject)(rawComponent.versions, val => _objects().Ref.from(val)),
      lang: rawComponent.lang,
      deprecated: rawComponent.deprecated,
      bindingPrefix: rawComponent.bindingPrefix,
      local: rawComponent.local,
      state: rawComponent.state
    });
  }

  static from(props) {
    return new Component(props);
  }

  static fromBitId(bitId) {
    if (bitId.box) throw new Error('component.fromBitId, bitId should not have the "box" property populated');
    return new Component({
      name: bitId.name,
      scope: bitId.scope
    });
  }

  validate() {
    const message = 'unable to save Component object';
    if (!this.name) throw new (_generalError().default)(`${message} the name is missing`);
  }

}

exports.default = Component;