/**
* Parse [N] and [N,M] citation markers in text and replace with React anchor links.
*
* Citations are 1-based indices into the key_moments array.
* Each marker becomes a superscript link to #km-{momentId}.
*/
import React from "react";
import type { KeyMomentSummary } from "../api/public-client";
// Matches [1], [2,3], [1,2,3], etc.
const CITATION_RE = /\[(\d+(?:,\s*\d+)*)\]/g;
/**
* Convert a text string containing [N] or [N,M] markers into an array of
* React nodes — plain strings interleaved with citation anchor elements.
*/
export function parseCitations(
text: string,
keyMoments: KeyMomentSummary[],
): React.ReactNode[] {
const nodes: React.ReactNode[] = [];
let lastIndex = 0;
for (const match of text.matchAll(CITATION_RE)) {
const matchStart = match.index ?? 0;
// Push text before this match
if (matchStart > lastIndex) {
nodes.push(text.slice(lastIndex, matchStart));
}
// Parse the indices from the match group
const rawGroup = match[1];
if (!rawGroup) continue;
const indices = rawGroup.split(",").map((s) => parseInt(s.trim(), 10));
const links: React.ReactNode[] = [];
for (let i = 0; i < indices.length; i++) {
const idx = indices[i]!;
// Citation indices are 1-based; key_moments array is 0-based
const moment = keyMoments[idx - 1];
if (moment) {
if (i > 0) links.push(", ");
links.push(
{idx}
,
);
} else {
// Invalid index — render as plain text
if (i > 0) links.push(", ");
links.push(String(idx));
}
}
nodes.push(
[{links}]
,
);
lastIndex = matchStart + match[0].length;
}
// Push remaining text after the last match
if (lastIndex < text.length) {
nodes.push(text.slice(lastIndex));
}
return nodes.length > 0 ? nodes : [text];
}