const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu
const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source)

const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u

/**
 * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
 * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
 *
 * @param {string} string The string to escape.
 * @returns {string} Returns the escaped string.
 */
function escape(string) {
  return string && RE_HAS_REGEXP_CHAR.test(string)
    ? string.replace(RE_REGEXP_CHAR, String.raw`\$&`)
    : string
}

/**
 * Convert a string to the `RegExp`.
 * Normal strings (e.g. `"foo"`) is converted to `/^foo$/` of `RegExp`.
 * Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`.
 *
 * @param {string} string The string to convert.
 * @param {{add?: string, remove?: string}} [flags] The flags to add or remove.
 *   - `add`: Flags to add to the `RegExp` (e.g. `'i'` for case-insensitive).
 *   - `remove`: Flags to remove from the `RegExp` (e.g. `'g'` to remove global matching).
 * @returns {RegExp} Returns the `RegExp`.
 */
function toRegExp(string, flags = {}) {
  const parts = RE_REGEXP_STR.exec(string)
  const { add: forceAddFlags = '', remove: forceRemoveFlags = '' } =
    typeof flags === 'object' ? flags : {} // Avoid issues when this is called directly from array.map
  if (parts) {
    return new RegExp(
      parts[1],
      parts[2].replace(
        new RegExp(`[${forceAddFlags}${forceRemoveFlags}]`, 'g'),
        ''
      ) + forceAddFlags
    )
  }
  return new RegExp(`^${escape(string)}$`, forceAddFlags)
}

/**
 * Checks whether given string is regexp string
 * @param {string} string
 * @returns {boolean}
 */
function isRegExp(string) {
  return RE_REGEXP_STR.test(string)
}

/**
 * Converts an array of strings to a singular function to match any of them.
 * This function converts each string to a `RegExp` and returns a function that checks all of them.
 *
 * @param {string[]} [patterns] The strings or regular expression strings to match.
 * @returns {(...toCheck: string[]) => boolean} Returns a function that checks if any string matches any of the given patterns.
 */
function toRegExpGroupMatcher(patterns = []) {
  if (patterns.length === 0) {
    return () => false
  }

  // In the future, we could optimize this by joining expressions with identical flags.
  const regexps = patterns.map((pattern) => toRegExp(pattern, { remove: 'g' }))

  if (regexps.length === 1) {
    return (...toCheck) => toCheck.some((str) => regexps[0].test(str))
  }

  return (...toCheck) =>
    regexps.some((regexp) => toCheck.some((str) => regexp.test(str)))
}

module.exports = {
  escape,
  toRegExp,
  isRegExp,
  toRegExpGroupMatcher
}
