"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.combinePatterns = combinePatterns;
exports.joinPatternSegments = joinPatternSegments;
exports.replaceWhen = replaceWhen;
var _last2 = _interopRequireDefault(require("lodash/last"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
 * Joins an array of regular expressions into a single expression, without
 * altering the received expressions.
 */
function joinPatternSegments(patterns) {
  return patterns.map(p => p.source).join('');
}

/**
 * Combines an array of regular expressions into a single expression, wrapping
 * each in a non-capturing group and interposing alternation characters (|) so
 * that each expression is executed separately.
 */
function combinePatterns(patterns) {
  return patterns.map(p => `(?:${p.source})`).join('|');
}

/**
 * Modify substrings within a string if they match a (global) pattern. Can be
 * inverted to only modify non-matches.
 *
 * params:
 * matchPattern - regexp - a regular expression to check for matches
 * replaceFn - function - a replacement function that receives a matched
 *   substring and returns a replacement substring
 * text - string - the string to process
 * invertMatchPattern - boolean - if true, non-matching substrings are modified
 *   instead of matching substrings
 */
function replaceWhen(matchPattern, replaceFn, text, invertMatchPattern) {
  /**
   * Splits the string into an array of objects with the following shape:
   *
   * {
   *   index: number - the index of the substring within the string
   *   text: string - the substring
   *   match: boolean - true if the substring matched `matchPattern`
   * }
   *
   * Loops through matches via recursion (`RegExp.exec` tracks the loop
   * internally).
   */
  function split(exp, text, acc) {
    /**
     * Get the next match starting from the end of the last match or start of
     * string.
     */
    const match = exp.exec(text);
    const lastEntry = (0, _last2.default)(acc);

    /**
     * `match` will be null if there are no matches.
     */
    if (!match) return acc;

    /**
     * If the match is at the beginning of the input string, normalize to a data
     * object with the `match` flag set to `true`, and add to the accumulator.
     */
    if (match.index === 0) {
      addSubstring(acc, 0, match[0], true);
    } else if (!lastEntry) {
      /**
       * If there are no entries in the accumulator, convert the substring before
       * the match to a data object (without the `match` flag set to true) and
       * push to the accumulator, followed by a data object for the matching
       * substring.
       */
      addSubstring(acc, 0, match.input.slice(0, match.index));
      addSubstring(acc, match.index, match[0], true);
    } else if (match.index === lastEntry.index + lastEntry.text.length) {
      /**
       * If the last entry in the accumulator immediately preceded the current
       * matched substring in the original string, just add the data object for
       * the matching substring to the accumulator.
       */
      addSubstring(acc, match.index, match[0], true);
    } else {
      /**
       * Convert the substring before the match to a data object (without the
       * `match` flag set to true), followed by a data object for the matching
       * substring.
       */
      const nextIndex = lastEntry.index + lastEntry.text.length;
      const nextText = match.input.slice(nextIndex, match.index);
      addSubstring(acc, nextIndex, nextText);
      addSubstring(acc, match.index, match[0], true);
    }

    /**
     * Continue executing the expression.
     */
    return split(exp, text, acc);
  }

  /**
   * Factory for converting substrings to data objects and adding to an output
   * array.
   */
  function addSubstring(arr, index, text, match = false) {
    arr.push({
      index,
      text,
      match
    });
  }

  /**
   * Split the input string to an array of data objects, each representing a
   * matching or non-matching string.
   */
  const acc = split(matchPattern, text, []);

  /**
   * Process the trailing substring after the final match, if one exists.
   */
  const lastEntry = (0, _last2.default)(acc);
  if (!lastEntry) return replaceFn(text);
  const nextIndex = lastEntry.index + lastEntry.text.length;
  if (text.length > nextIndex) {
    acc.push({
      index: nextIndex,
      text: text.slice(nextIndex)
    });
  }

  /**
   * Map the data objects in the accumulator to their string values, modifying
   * matched strings with the replacement function. Modifies non-matches if
   * `invertMatchPattern` is truthy.
   */
  const replacedText = acc.map(entry => {
    const isMatch = invertMatchPattern ? !entry.match : entry.match;
    return isMatch ? replaceFn(entry.text) : entry.text;
  });

  /**
   * Return the joined string.
   */
  return replacedText.join('');
}