itemsToRegexp = (itemsArr) => {
  if (itemsArr.length === 1) {
    return quoteMeta(itemsArr[0]);
  }

  /** @type {Array<string>} */
  const finishedItems = [];

  // merge single char items: (a|b|c|d|ef) => ([abcd]|ef)
  let countOfSingleCharItems = 0;

  for (const item of itemsArr) {
    if (item.length === 1) {
      // eslint-disable-next-line no-plusplus
      countOfSingleCharItems++;
    }
  }

  // special case for only single char items
  if (countOfSingleCharItems === itemsArr.length) {
    return `[${quoteMeta(itemsArr.sort().join(""))}]`;
  }

  const items = new Set(itemsArr.sort());

  if (countOfSingleCharItems > 2) {
    let singleCharItems = "";
    for (const item of items) {
      if (item.length === 1) {
        singleCharItems += item;
        items.delete(item);
      }
    }
    finishedItems.push(`[${quoteMeta(singleCharItems)}]`);
  }

  // special case for 2 items with common prefix/suffix
  if (finishedItems.length === 0 && items.size === 2) {
    const prefix = getCommonPrefix(itemsArr);
    const suffix = getCommonSuffix(
      itemsArr.map((item) => item.slice(prefix.length))
    );

    if (prefix.length > 0 || suffix.length > 0) {
      return `${quoteMeta(prefix)}${itemsToRegexp(
        itemsArr.map((i) => i.slice(prefix.length, -suffix.length || undefined))
      )}${quoteMeta(suffix)}`;
    }
  }

  // special case for 2 items with common suffix
  if (finishedItems.length === 0 && items.size === 2) {
    /** @type {Iterator<string>} */
    const it = items[Symbol.iterator]();
    const a = it.next().value;
    const b = it.next().value;
    if (a.length > 0 && b.length > 0 && a.slice(-1) === b.slice(-1)) {
      return `${itemsToRegexp([a.slice(0, -1), b.slice(0, -1)])}${quoteMeta(
        a.slice(-1)
      )}`;
    }
  }

  // find common prefix: (a1|a2|a3|a4|b5) => (a(1|2|3|4)|b5)
  const prefixed = popCommonItems(
    items,
    (item) => (item.length >= 1 ? item[0] : false),
    (list) => {
      if (list.length >= 3) return true;
      if (list.length <= 1) return false;
      return list[0][1] === list[1][1];
    }
  );
  for (const prefixedItems of prefixed) {
    const prefix = getCommonPrefix(prefixedItems);
    finishedItems.push(
      `${quoteMeta(prefix)}${itemsToRegexp(
        prefixedItems.map((i) => i.slice(prefix.length))
      )}`
    );
  }

  // find common suffix: (a1|b1|c1|d1|e2) => ((a|b|c|d)1|e2)
  const suffixed = popCommonItems(
    items,
    (item) => (item.length >= 1 ? item.slice(-1) : false),
    (list) => {
      if (list.length >= 3) return true;
      if (list.length <= 1) return false;
      return list[0].slice(-2) === list[1].slice(-2);
    }
  );
  for (const suffixedItems of suffixed) {
    const suffix = getCommonSuffix(suffixedItems);
    finishedItems.push(
      `${itemsToRegexp(
        suffixedItems.map((i) => i.slice(0, -suffix.length))
      )}${quoteMeta(suffix)}`
    );
  }

  // TODO further optimize regexp, i. e.
  // use ranges: (1|2|3|4|a) => [1-4a]
  const conditional = finishedItems.concat(Array.from(items, quoteMeta));
  if (conditional.length === 1) return conditional[0];
  return `(${conditional.join("|")})`;
}
@param {Array<string>} itemsArr array of items
@returns {string} regexp