"use strict";

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

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

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

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

  _ramda = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

function _hasWildcard() {
  const data = _interopRequireDefault(require("../../utils/string/has-wildcard"));

  _hasWildcard = function () {
    return data;
  };

  return data;
}

function _isBitIdMatchByWildcards() {
  const data = _interopRequireDefault(require("../../utils/bit/is-bit-id-match-by-wildcards"));

  _isBitIdMatchByWildcards = function () {
    return data;
  };

  return data;
}

function _validateType() {
  const data = require("../../utils/validate-type");

  _validateType = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

function _abstractConfig() {
  const data = _interopRequireDefault(require("./abstract-config"));

  _abstractConfig = function () {
    return data;
  };

  return data;
}

const dependenciesFields = ['dependencies', 'devDependencies', 'peerDependencies'];
exports.dependenciesFields = dependenciesFields;
const consumerOverridesPermittedFields = [...dependenciesFields, 'env'];

class ConsumerOverrides {
  // whether the overrides has been changed (so then it should write them to fs)
  constructor(overrides) {
    this.overrides = overrides;
  }

  static load(overrides = {}) {
    return new ConsumerOverrides(overrides);
  }

  getOverrideComponentData(bitId) {
    const getMatches = () => {
      const exactMatch = this.findExactMatch(bitId);
      const matchByGlobPattern = Object.keys(this.overrides).filter(idStr => this.isMatchByWildcard(bitId, idStr));
      const allMatches = matchByGlobPattern.sort(ConsumerOverrides.sortWildcards);
      if (exactMatch) allMatches.unshift(exactMatch);
      return allMatches;
    };

    const matches = getMatches();
    if (!matches.length) return null;
    const overrideValues = matches.map(match => this.overrides[match]);
    let stopPropagation = false;
    return overrideValues.reduce((acc, current) => {
      if (stopPropagation) return acc;

      if (!current.propagate) {
        stopPropagation = true;
      }

      consumerOverridesPermittedFields.forEach(field => {
        if (!current[field]) return;
        if (!acc[field]) acc[field] = {};

        if (field === 'env') {
          ['compiler', 'tester'].forEach(envField => {
            // $FlowFixMe we made sure before that current.env is set
            if (acc.env[envField] || !current.env[envField]) return;
            acc.env[envField] = current.env[envField];
          });
        } else if (dependenciesFields.includes(field)) {
          // $FlowFixMe
          acc[field] = Object.assign(current[field], acc[field]);
        } else {
          throw new Error(`consumer-overrides, ${field} does not have a merge strategy`);
        }
      });
      return acc;
    }, {});
  }

  isMatchByWildcard(bitId, idWithPossibleWildcard) {
    if (!(0, _hasWildcard().default)(idWithPossibleWildcard)) return false;
    return (0, _isBitIdMatchByWildcards().default)(bitId, idWithPossibleWildcard);
  }
  /**
   * sort from the more specific (more namespaces) to the more generic (less namespaces)
   * e.g.
   * src/utils/javascript/*
   * src/utils/javascript/*
   * src/utils/*
   * src/*
   *
   * more namespaces (slashes) === more specific
   * more wildcards === less specific
   *
   * if both have the same number of namespaces (slashes), the one with less wildcards is first.
   * if both have the same number of wildcards, the one with more namespaces is first.
   *
   * a reminder about compare function:
   * If the result is negative a is sorted before b.
   * If the result is positive b is sorted before a.
   * If the result is 0 no changes is done with the sort order of the two values.
   */


  static sortWildcards(a, b) {
    const numOfNamespaces = str => (str.match(/\//g) || []).length;

    const numOfWildcards = str => (str.match(/\*/g) || []).length;

    const indexOfFirstWildcard = str => str.indexOf('*');

    const byNamespaces = numOfNamespaces(b) - numOfNamespaces(a);
    if (byNamespaces !== 0) return byNamespaces;
    const byWildcards = numOfWildcards(a) - numOfWildcards(b);
    if (byWildcards !== 0) return byWildcards; // both have the same number of namespaces and the same number of wildcards
    // e.g. a component `utils/is-string` matches two rules: `utils/*` and `*/is-string`.
    // the one with the wildcard more left should be first as it is more specific.

    return indexOfFirstWildcard(a) - indexOfFirstWildcard(b);
  }

  updateOverridesIfChanged(component, areEnvsChanged) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const overrides = component.overrides.componentOverridesData;
      const id = component.id;

      const existingOverrides = _this.getOverrideComponentData(id);

      if (!areEnvsChanged && _this.areOverridesObjectsEqual(existingOverrides, overrides)) return false;

      const exactMatch = _this.findExactMatch(id);

      const key = exactMatch || id.toStringWithoutVersion();
      const env = {};

      if (component.compiler) {
        env.compiler = _abstractConfig().default.convertEnvToStringIfPossible(component.compiler.toBitJsonObject('.'));
      }

      if (component.tester) {
        env.tester = _abstractConfig().default.convertEnvToStringIfPossible(component.tester.toBitJsonObject('.'));
      }

      if (!_ramda().default.isEmpty(env)) overrides.env = env;
      _this.overrides[key] = overrides;
      _this.hasChanged = true;
      return true;
    })();
  }

  areOverridesObjectsEqual(overridesA, overridesB) {
    // seems like R.equals does a great job here. it compares objects by values (not by reference).
    // also it disregards the keys order.
    return _ramda().default.equals(overridesA, overridesB);
  }

  findExactMatch(bitId) {
    return Object.keys(this.overrides).find(idStr => bitId.toStringWithoutVersion() === idStr || bitId.toStringWithoutScopeAndVersion() === idStr);
  }

  removeExactMatch(bitId) {
    const exactMatch = this.findExactMatch(bitId);
    if (!exactMatch) return false;
    delete this.overrides[exactMatch];
    return true;
  }

  static validate(overrides) {
    if (typeof overrides === 'undefined') return;
    const message = 'consumer-config (either bit.json or package.json "bit")';
    (0, _validateType().validateUserInputType)(message, overrides, 'overrides', 'object');
    Object.keys(overrides).forEach(id => validateComponentOverride(id, overrides[id]));

    function validateComponentOverride(id, override) {
      (0, _validateType().validateUserInputType)(message, override, `overrides.${id}`, 'object');
      Object.keys(override).forEach(field => {
        if (!consumerOverridesPermittedFields.includes(field)) {
          throw new (_generalError().default)(`${message} found an unrecognized field "${field}" inside "overrides.${id}" property.
only the following fields are allowed: ${consumerOverridesPermittedFields.join(', ')}.`);
        }

        if (dependenciesFields.includes(field)) {
          validateDependencyField(field, override, id);
        } else if (field === 'env') {
          validateEnv(override, id);
        }
      });
    }

    function validateDependencyField(field, override, id) {
      (0, _validateType().validateUserInputType)(message, override[field], `overrides.${id}.${field}`, 'object');
      Object.keys(override[field]).forEach(rule => {
        (0, _validateType().validateUserInputType)(message, override[field][rule], `overrides.${id}.${field}.${rule}`, 'string');
      });
    }

    function validateEnv(override, id) {
      (0, _validateType().validateUserInputType)(message, override.env, `overrides.${id}.env`, 'object');
    }
  }

}

exports.default = ConsumerOverrides;