/**
 * emojiFlood.js
 *
 * Utilities for detecting and collapsing emoji flood content on the client.
 *
 * These mirror the PHP-side logic in LegacySmileyMapper::collapseFlood() and
 * ContentSanitizer::validate() so that the UI can apply the same rules without
 * a round-trip to the server.
 */

// ── Constants ─────────────────────────────────────────────────────────────────

/** Absolute emoji count above which text is considered a flood for display. */
export const FLOOD_COUNT_THRESHOLD = 20

/** Ratio of emoji / total chars above which text is a density flood. */
export const FLOOD_DENSITY_THRESHOLD = 0.40

/** Maximum consecutive identical emoji kept before the rest are collapsed. */
export const COLLAPSE_MAX_RUN = 5

// ── Emoji detection ───────────────────────────────────────────────────────────

/**
 * Regex that matches a single emoji "unit":
 *   • Codepoints U+1F000–U+1FFFF (most modern emoji)
 *   • Codepoints U+2600–U+27BF  (misc symbols, dingbats)
 *   • Optionally followed by U+FE0F variation selector
 *
 * Note: ZWJ sequences and flags are not fully modelled here, but those
 * multi-codepoint emoji are extremely unlikely in legacy flood spam.
 */
const EMOJI_UNIT_RE = /[\u{1F000}-\u{1FFFF}\u{2600}-\u{27BF}]\uFE0F?/gu

/**
 * Count emoji in a string.
 * @param {string} text
 * @returns {number}
 */
export function countEmoji(text) {
  if (!text) return 0
  return (text.match(EMOJI_UNIT_RE) || []).length
}

// ── Flood detection ───────────────────────────────────────────────────────────

/**
 * Returns true when the text qualifies as an emoji flood.
 *
 * Two independent flood signals:
 *  1. Absolute count > FLOOD_COUNT_THRESHOLD emoji  (e.g. 21 beer mugs)
 *  2. Density > FLOOD_DENSITY_THRESHOLD  (e.g. "🍺🍺🍺🍺" — 80% emoji, 0 text)
 *     Only applied when count > 5 to avoid false-positives on short strings.
 *
 * @param {string}  text
 * @param {object}  [opts]
 * @param {number}  [opts.maxCount=FLOOD_COUNT_THRESHOLD]
 * @param {number}  [opts.maxDensity=FLOOD_DENSITY_THRESHOLD]
 * @returns {boolean}
 */
export function isFlood(text, { maxCount = FLOOD_COUNT_THRESHOLD, maxDensity = FLOOD_DENSITY_THRESHOLD } = {}) {
  if (!text) return false
  const count = countEmoji(text)
  if (count > maxCount) return true
  if (count > 5 && text.length > 0 && count / text.length > maxDensity) return true
  return false
}

// ── Flood collapsing ──────────────────────────────────────────────────────────

/**
 * Collapse consecutive runs of the same emoji that exceed `maxRun` repetitions.
 *
 * Behaviour mirrors LegacySmileyMapper::collapseFlood() on the PHP side:
 *   "🍺 🍺 🍺 🍺 🍺 🍺 🍺 🍺"  (8×) → "🍺 🍺 🍺 🍺 🍺 ×8"
 *
 * Algorithm:
 *  1. Scan the string for all emoji "units" (codepoint + optional variation selector).
 *  2. Group consecutive matches of the SAME emoji where the gap between each
 *     pair is pure horizontal whitespace (space / tab / no gap at all).
 *  3. Runs longer than `maxRun` are replaced with `maxRun` copies + " ×N".
 *
 * @param {string} text
 * @param {number} [maxRun=COLLAPSE_MAX_RUN]
 * @returns {string}
 */
export function collapseEmojiRuns(text, maxRun = COLLAPSE_MAX_RUN) {
  if (!text) return text

  // Step 1 – locate every emoji unit in the string.
  const unitRe = /[\u{1F000}-\u{1FFFF}\u{2600}-\u{27BF}]\uFE0F?/gu
  const hits   = []
  let m
  while ((m = unitRe.exec(text)) !== null) {
    hits.push({ index: m.index, val: m[0], end: m.index + m[0].length })
  }
  if (hits.length === 0) return text

  // Step 2 – group hits into "same-emoji runs separated only by whitespace".
  const runs = []
  let i = 0
  while (i < hits.length) {
    let j = i
    while (
      j + 1 < hits.length &&
      hits[j + 1].val === hits[i].val &&
      /^[ \t]*$/.test(text.slice(hits[j].end, hits[j + 1].index))
    ) {
      j++
    }
    runs.push({ from: i, to: j, emoji: hits[i].val })
    i = j + 1
  }

  // Step 3 – rebuild the string, collapsing long runs.
  if (!runs.some((r) => r.to - r.from + 1 > maxRun)) return text

  let result = ''
  let pos    = 0
  for (const run of runs) {
    const count     = run.to - run.from + 1
    const spanStart = hits[run.from].index
    const spanEnd   = hits[run.to].end

    result += text.slice(pos, spanStart)

    if (count > maxRun) {
      result += Array(maxRun).fill(run.emoji).join(' ') + ' ×' + count
    } else {
      result += text.slice(spanStart, spanEnd)
    }

    pos = spanEnd
  }
  result += text.slice(pos)

  return result
}
