// Canonical fediverse text patterns — the single source of truth for
// URL / mention / hashtag / custom-emoji matching across the client.
//
// These mirror Pixelfed's server-side lexer `app/Util/Lexer/Regex.php`
// (Apache 2.0, a Twitter-Text fork) — the same code that emits the anchors
// in `Status.content`. Mirroring keeps client-side autolinking aligned with
// what the server already linkified, so anything Pixelfed turns into an
// anchor at post time also linkifies here. Mastodon's `app/models/tag.rb`
// + `account.rb` agree on the parts that matter (a hashtag needs ≥1 letter
// and may carry digits at either end; mentions reject emails / URL paths),
// so a single pattern serves both protocols.
//
// This module is intentionally PURE — no react-native / tamagui / mmkv /
// nitro imports. Both the rendering primitive (`AutolinkText.tsx`, which
// transitively pulls native deps — see its Favicon lazy-require) and the
// plain-text helpers in `src/utils.ts` import these patterns. Keeping them
// in a leaf module lets `utils.ts` reuse the hashtag definition without
// dragging the autolinker's native graph into jest-expo.
//
// Note: the composer's character counter (`./count-characters.ts`)
// deliberately keeps its OWN url regex and does NOT share this set — its
// requirement is drift-resistance on a per-keystroke hot path, not
// server-parity. Don't consolidate that one in here.
//
// Hermes (RN 0.83 / SDK 55) supports lookbehind and `\p{…}` Unicode classes
// with the `u` flag, so these can be expressed inline.
//
//  - URL host class is unbounded in TLD length (so `.community`,
//    `.photography`, IDN `.amsterdam` all link). Path/query/fragment accept
//    the RFC-3986 sub-delim set Regex.php inherits from Twitter-Text.
//    Balanced parens (one level) survive in paths so Wikipedia URLs like
//    `…/wiki/Primer_(film)` stay intact. Trailing sentence punctuation is
//    stripped after the match via `trimUrlTail` rather than in the regex.
//  - Mention uses a leading lookbehind so `(@alice`, `[@alice`, em-dash +
//    mention, and start-of-string all link, while emails (`foo@bar.com`)
//    and the `@user` segment inside a URL path are rejected. Username class
//    is Unicode-letter inclusive up to 30 chars (Mastodon's looser cap).
//  - Hashtag mirrors `$tmp['hashtag']` (Regex.php:155): an optional run of
//    letters/digits/specials, then a REQUIRED letter (or combining mark),
//    then another optional run — i.e. a hashtag must contain at least one
//    letter SOMEWHERE, with digits allowed at the start AND the end. So
//    `#a`, `#a1`, `#2nd`, `#100DaysOfCode`, `#test123` all match in full,
//    while `#1` / `#123` (pure-numeric) do not. The keycap-sequence
//    lookahead excludes emoji-keycap bases (`1️⃣`). Total length is bounded
//    (Pixelfed's Extractor.php caps real tags at 124).

// ---- Hashtag (Regex.php:130-155) ------------------------------------------
// Built from shared source strings so the autolinker, the trailing-pill
// tokenizer, and the "is this line all hashtags?" test all agree on what a
// hashtag is.
const HASHTAG_LETTERS = '\\p{L}\\p{M}'
const HASHTAG_NUMERALS = '\\p{Nd}' // server uses \p{Nd} (decimal), not \p{N}
// The full `hashtag_special_chars` set from Regex.php:151 — underscore,
// ZWNJ/ZWJ, Cyrillic kavyka, Hebrew maqaf/geresh/gershayim, fullwidth tilde,
// wave dash, katakana sound/double-hyphen/middle-dot marks, ditto, Tibetan
// tshegs, middle dot.
const HASHTAG_SPECIALS =
  '_\\u{200C}\\u{200D}\\u{A67E}\\u{05BE}\\u{05F3}\\u{05F4}\\u{FF5E}\\u{301C}\\u{309B}\\u{309C}\\u{30A0}\\u{30FB}\\u{3003}\\u{0F0B}\\u{0F0C}\\u{00B7}'
const HASHTAG_SET = `[${HASHTAG_LETTERS}${HASHTAG_NUMERALS}${HASHTAG_SPECIALS}]`
// Faithful to the server's `set* letter set*` (a hashtag needs ≥1 letter
// somewhere, with digits allowed at the start AND the end), but written as a
// letter-presence LOOKAHEAD followed by a single greedy run so it stays
// linear-time. The literal two-star form `set*[letter]set*` backtracks
// catastrophically once wrapped in the `(?:…)+` of `hashtagOnlyLine` (a line
// of many near-tags partitions exponentially). `(?=set{0,123}[letter])`
// asserts a letter exists within the length cap without consuming; the
// `set{1,124}` run then consumes the whole tag unambiguously (one way to
// match a maximal run → no partition explosion). 124 mirrors Extractor.php.
const HASHTAG_BODY = `(?=${HASHTAG_SET}{0,123}[${HASHTAG_LETTERS}])${HASHTAG_SET}{1,124}`

export const hashtagRegex = new RegExp(
  `(?<![&${HASHTAG_LETTERS}${HASHTAG_NUMERALS}${HASHTAG_SPECIALS}])#(?![\\u{FE0F}\\u{20E3}])${HASHTAG_BODY}`,
  'gu'
)

// For `extractTrailingHashtags` in `src/utils.ts`: a global token matcher to
// iterate hashtags within a trailing block, and an anchored test for whether
// a whole line is nothing but hashtags (optionally separated by whitespace or
// commas). Both reuse `HASHTAG_BODY` so the pill extractor and the inline
// autolinker can never disagree on tag boundaries.
export const hashtagToken = new RegExp(`#${HASHTAG_BODY}`, 'gu')
export const hashtagOnlyLine = new RegExp(`^\\s*(?:#${HASHTAG_BODY}[\\s,]*)+$`, 'u')

// ---- URL (Regex.php valid_url / Twitter-Text) -----------------------------
export const linkRegex =
  /https?:\/\/(?:www\.)?[-\p{L}\p{N}@:%._+~#=]{1,256}\.[\p{L}\p{N}]{2,}(?:[-\p{L}\p{N}@:%_+.~#?&/=!*,;'|]|\([-\p{L}\p{N}@:%_+.~#?&/=!*,;'|]+\))*/gu

// ---- Mention (Regex.php valid_mentions_or_lists) --------------------------
export const mentionRegex =
  /(?<![\p{L}\p{N}_/:])@[\p{L}\p{N}_\-.]{1,30}(?:@[\p{L}\p{N}\-.]+\.[\p{L}\p{N}]{2,})?/gu

// ---- Custom emoji shortcode -----------------------------------------------
export const emojiRegex = /:([a-zA-Z0-9_]{2,}):/g

// Greedy URL matching can sweep trailing sentence punctuation (`.`, `,`,
// `!`, `?`, `;`, `:`) into the match — `See https://x.org.` would otherwise
// leak the period into the link. Strip it after the fact rather than
// expressing the constraint inside the regex (the alternative is a "safe
// ending char" anchor that breaks paths ending in a balanced `(…)` group).
const TRAILING_URL_PUNCT = /[.,;:!?]+$/

export function trimUrlTail(url: string): string {
  return url.replace(TRAILING_URL_PUNCT, '')
}
