'use strict';

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

var _regenerator;

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

var _bluebird;

function _load_bluebird() {
  return _bluebird = require('bluebird');
}

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

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

var _bitId;

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

var _hasWildcard;

function _load_hasWildcard() {
  return _hasWildcard = _interopRequireDefault(require('../../utils/string/has-wildcard'));
}

var _isBitIdMatchByWildcards;

function _load_isBitIdMatchByWildcards() {
  return _isBitIdMatchByWildcards = _interopRequireDefault(require('../../utils/bit/is-bit-id-match-by-wildcards'));
}

var _validateType;

function _load_validateType() {
  return _validateType = require('../../utils/validate-type');
}

var _generalError;

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

var _abstractConfig;

function _load_abstractConfig() {
  return _abstractConfig = _interopRequireDefault(require('./abstract-config'));
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var dependenciesFields = exports.dependenciesFields = ['dependencies', 'devDependencies', 'peerDependencies'];
var consumerOverridesPermittedFields = [].concat(dependenciesFields, ['env']);

var ConsumerOverrides = function () {
  // whether the overrides has been changed (so then it should write them to fs)
  function ConsumerOverrides(overrides) {
    _classCallCheck(this, ConsumerOverrides);

    this.overrides = overrides;
  }

  _createClass(ConsumerOverrides, [{
    key: 'getOverrideComponentData',
    value: function getOverrideComponentData(bitId) {
      var _this = this;

      var getMatches = function getMatches() {
        var exactMatch = _this.findExactMatch(bitId);
        var matchByGlobPattern = Object.keys(_this.overrides).filter(function (idStr) {
          return _this.isMatchByWildcard(bitId, idStr);
        });
        var allMatches = matchByGlobPattern.sort(ConsumerOverrides.sortWildcards);
        if (exactMatch) allMatches.unshift(exactMatch);
        return allMatches;
      };
      var matches = getMatches();
      if (!matches.length) return null;
      var overrideValues = matches.map(function (match) {
        return _this.overrides[match];
      });
      var stopPropagation = false;
      return overrideValues.reduce(function (acc, current) {
        if (stopPropagation) return acc;
        if (!current.propagate) {
          stopPropagation = true;
        }
        consumerOverridesPermittedFields.forEach(function (field) {
          if (!current[field]) return;
          if (!acc[field]) acc[field] = {};
          if (field === 'env') {
            ['compiler', 'tester'].forEach(function (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;
      }, {});
    }
  }, {
    key: 'isMatchByWildcard',
    value: function isMatchByWildcard(bitId, idWithPossibleWildcard) {
      if (!(0, (_hasWildcard || _load_hasWildcard()).default)(idWithPossibleWildcard)) return false;
      return (0, (_isBitIdMatchByWildcards || _load_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.
     */

  }, {
    key: 'updateOverridesIfChanged',
    value: function () {
      var _ref = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(component, areEnvsChanged) {
        var overrides, id, existingOverrides, exactMatch, key, env;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                overrides = component.overrides.componentOverridesData;
                id = component.id;
                existingOverrides = this.getOverrideComponentData(id);

                if (!(!areEnvsChanged && this.areOverridesObjectsEqual(existingOverrides, overrides))) {
                  _context.next = 5;
                  break;
                }

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

              case 5:
                exactMatch = this.findExactMatch(id);
                key = exactMatch || id.toStringWithoutVersion();
                env = {};

                if (component.compiler) {
                  env.compiler = (_abstractConfig || _load_abstractConfig()).default.convertEnvToStringIfPossible(component.compiler.toBitJsonObject('.'));
                }
                if (component.tester) {
                  env.tester = (_abstractConfig || _load_abstractConfig()).default.convertEnvToStringIfPossible(component.tester.toBitJsonObject('.'));
                }
                if (!(_ramda || _load_ramda()).default.isEmpty(env)) overrides.env = env;
                this.overrides[key] = overrides;
                this.hasChanged = true;
                return _context.abrupt('return', true);

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

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

      return updateOverridesIfChanged;
    }()
  }, {
    key: 'areOverridesObjectsEqual',
    value: function 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 || _load_ramda()).default.equals(overridesA, overridesB);
    }
  }, {
    key: 'findExactMatch',
    value: function findExactMatch(bitId) {
      return Object.keys(this.overrides).find(function (idStr) {
        return bitId.toStringWithoutVersion() === idStr || bitId.toStringWithoutScopeAndVersion() === idStr;
      });
    }
  }, {
    key: 'removeExactMatch',
    value: function removeExactMatch(bitId) {
      var exactMatch = this.findExactMatch(bitId);
      if (!exactMatch) return false;
      delete this.overrides[exactMatch];
      return true;
    }
  }], [{
    key: 'load',
    value: function load() {
      var overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

      return new ConsumerOverrides(overrides);
    }
  }, {
    key: 'sortWildcards',
    value: function sortWildcards(a, b) {
      var numOfNamespaces = function numOfNamespaces(str) {
        return (str.match(/\//g) || []).length;
      };
      var numOfWildcards = function numOfWildcards(str) {
        return (str.match(/\*/g) || []).length;
      };
      var indexOfFirstWildcard = function indexOfFirstWildcard(str) {
        return str.indexOf('*');
      };
      var byNamespaces = numOfNamespaces(b) - numOfNamespaces(a);
      if (byNamespaces !== 0) return byNamespaces;
      var 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);
    }
  }, {
    key: 'validate',
    value: function validate(overrides) {
      if (typeof overrides === 'undefined') return;
      var message = 'consumer-config (either bit.json or package.json "bit")';
      (0, (_validateType || _load_validateType()).validateUserInputType)(message, overrides, 'overrides', 'object');
      Object.keys(overrides).forEach(function (id) {
        return validateComponentOverride(id, overrides[id]);
      });

      function validateComponentOverride(id, override) {
        (0, (_validateType || _load_validateType()).validateUserInputType)(message, override, `overrides.${id}`, 'object');
        Object.keys(override).forEach(function (field) {
          if (!consumerOverridesPermittedFields.includes(field)) {
            throw new (_generalError || _load_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 || _load_validateType()).validateUserInputType)(message, override[field], `overrides.${id}.${field}`, 'object');
        Object.keys(override[field]).forEach(function (rule) {
          (0, (_validateType || _load_validateType()).validateUserInputType)(message, override[field][rule], `overrides.${id}.${field}.${rule}`, 'string');
        });
      }
      function validateEnv(override, id) {
        (0, (_validateType || _load_validateType()).validateUserInputType)(message, override.env, `overrides.${id}.env`, 'object');
      }
    }
  }]);

  return ConsumerOverrides;
}();

exports.default = ConsumerOverrides;