// @handle mentions in post text, adapted from bluesky's facet model:
// facets store utf-16 index ranges into the text plus the mentioned user id,
// resolved once at publish so later handle changes don't break them

// charset mirrors HandleSchema ([a-z0-9] with single dashes), case-tolerant
// while typing; a mention only opens at line start, whitespace or "("
const MENTION_RE = /(^|\s|\()@([a-zA-Z0-9-]+)/g

// same shape but allows a bare "@" so autocomplete opens immediately
const MENTION_TRIGGER_RE = /(^|\s|\()@([a-zA-Z0-9-]*)/g

export type MentionFacet = {
  start: number
  end: number
  userId: string
}

export type DetectedMention = {
  start: number
  end: number
  handle: string
}

export type FacetSegment = {
  text: string
  facet?: MentionFacet
}

export const detectMentions = (text: string): DetectedMention[] =>
  [...text.matchAll(MENTION_RE)].map((match) => {
    const start = match.index + match[1]!.length
    return {
      start,
      end: start + match[2]!.length + 1,
      handle: match[2]!,
    }
  })

// the partial mention token the cursor sits inside, for autocomplete
export const getMentionAt = (
  text: string,
  cursor: number,
): {value: string; index: number} | undefined => {
  for (const match of text.matchAll(MENTION_TRIGGER_RE)) {
    const start = match.index + match[1]!.length
    const value = match[2]!
    if (cursor >= start && cursor <= start + value.length + 1)
      return {value, index: start}
  }
  return undefined
}

// replace the token at the cursor with the chosen handle; the trailing
// space keeps the next keystroke from reopening the autocomplete
export const insertMentionAt = (
  text: string,
  cursor: number,
  handle: string,
): string => {
  const target = getMentionAt(text, cursor)
  if (!target) return text
  const after = text.slice(target.index + target.value.length + 1)
  return `${text.slice(0, target.index)}@${handle} ${after.trimStart()}`
}

// publish-time resolution: match detected handles against the member set
// case-insensitively and silently drop the rest
export const resolveMentionFacets = (
  text: string,
  members: readonly {id: string; handle: string}[],
): MentionFacet[] => {
  const byHandle = new Map(
    members.map((m) => [m.handle.toLowerCase(), m.id]),
  )
  return detectMentions(text).flatMap(({start, end, handle}) => {
    const userId = byHandle.get(handle.toLowerCase())
    return userId ? [{start, end, userId}] : []
  })
}

// split text into alternating plain/faceted segments for rendering
export const segmentByFacets = (
  text: string,
  facets: readonly MentionFacet[],
): FacetSegment[] => {
  // sort the filtered copy in place: hermes has no toSorted, and the
  // caller's array stays untouched
  const valid = facets
    .filter(
      (f) => f.start >= 0 && f.end <= text.length && f.start < f.end,
    )
    .sort((a, b) => a.start - b.start)
  const segments: FacetSegment[] = []
  let cursor = 0
  for (const facet of valid) {
    if (facet.start < cursor) continue
    if (facet.start > cursor)
      segments.push({text: text.slice(cursor, facet.start)})
    segments.push({text: text.slice(facet.start, facet.end), facet})
    cursor = facet.end
  }
  if (cursor < text.length || segments.length === 0)
    segments.push({text: text.slice(cursor)})
  return segments
}
