import { Fragment, type ReactNode } from 'react'
import { kinds, type Event } from 'nostr-tools'
import { resolveNip84HighlightDisplay } from '@/lib/nip84-highlight-display'
/** Same classes as {@link Highlight} NIP-84 span marks (keep in sync manually). */
export const NIP84_HIGHLIGHT_MARK_CLASSNAME =
'bg-green-200 dark:bg-green-600 dark:text-white px-1 rounded font-medium'
function stripOuterQuotes(s: string): string {
let t = s.trim()
if (t.startsWith('"') && t.endsWith('"')) {
t = t.slice(1, -1).trim()
}
return t
}
function highlightTargetsNoteHex(h: Event, opHex: string): boolean {
const want = opHex.trim().toLowerCase()
if (!/^[0-9a-f]{64}$/i.test(want)) return false
return h.tags.some(
(t) => (t[0] === 'e' || t[0] === 'E') && typeof t[1] === 'string' && t[1].trim().toLowerCase() === want
)
}
/** Non-overlapping merged intervals for first occurrence of each highlight’s marked span in `baseText`. */
export function mergeNip84MarkedIntervals(
baseText: string,
highlightEvents: Event[],
opEventHexId: string
): { start: number; end: number }[] {
const intervals: { start: number; end: number }[] = []
for (const ev of highlightEvents) {
if (ev.kind !== kinds.Highlights) continue
if (!highlightTargetsNoteHex(ev, opEventHexId)) continue
const { markedSpan } = resolveNip84HighlightDisplay(ev)
const needle = stripOuterQuotes(markedSpan)
if (!needle) continue
const idx = baseText.indexOf(needle)
if (idx >= 0) intervals.push({ start: idx, end: idx + needle.length })
}
if (intervals.length === 0) return []
intervals.sort((a, b) => a.start - b.start)
const merged: { start: number; end: number }[] = []
for (const cur of intervals) {
const prev = merged[merged.length - 1]
if (!prev || cur.start > prev.end) merged.push({ ...cur })
else prev.end = Math.max(prev.end, cur.end)
}
return merged
}
export function renderPlaintextWithNip84MergedMarks(
baseText: string,
merged: { start: number; end: number }[]
): ReactNode {
if (merged.length === 0) return baseText
const nodes: ReactNode[] = []
let cursor = 0
let i = 0
for (const m of merged) {
if (cursor < m.start) nodes.push({baseText.slice(cursor, m.start)})
nodes.push(
{baseText.slice(m.start, m.end)}
)
cursor = m.end
}
if (cursor < baseText.length) nodes.push({baseText.slice(cursor)})
return <>{nodes}>
}