import DOMPurify from 'dompurify';

/**
 * Minimal, safe Markdown → HTML renderer for assistant prose.
 *
 * Supports the subset an LLM realistically emits: GFM pipe tables, bullet/ordered
 * lists, headings, bold/italic/inline-code and clickable links. Everything is
 * escaped first and run through DOMPurify, so model output can never inject HTML.
 * Data is normally shown via structured blocks; this keeps any stray Markdown
 * (tables, links) readable instead of dumping raw pipes on screen.
 */

const escapeHtml = (s: string): string =>
    s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

// Only allow http(s) and app-relative links — blocks javascript:, data:, etc.
const isSafeUrl = (url: string): boolean => /^(https?:\/\/|\/)/i.test(url.trim());

function inline(text: string): string {
    let s = escapeHtml(text);
    s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, label: string, url: string) =>
        isSafeUrl(url)
            ? `<a href="${url}" target="_blank" rel="noopener noreferrer" class="text-secondary underline underline-offset-2 hover:opacity-80">${label}</a>`
            : label,
    );
    s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
    s = s.replace(/`([^`]+)`/g, '<code class="rounded bg-muted px-1 py-0.5 text-[0.85em]">$1</code>');
    // Emphasis only when the asterisks hug the text (no adjacent space) — so an
    // arithmetic "5 * n" is left alone while "*mot*" italicises.
    s = s.replace(/(^|[^*\w])\*([^\s*](?:[^*\n]*?[^\s*])?)\*(?!\w)/g, '$1<em>$2</em>');
    return s;
}

const isTableRow = (l: string): boolean => /^\s*\|.*\|\s*$/.test(l);
const isTableSeparator = (l: string): boolean => /^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(l);

const splitRow = (l: string): string[] =>
    l.trim().replace(/^\||\|$/g, '').split('|').map((c) => c.trim());

function renderTable(header: string, rows: string[][]): string {
    const head = splitRow(header)
        .map((c) => `<th class="px-3 py-2 text-left font-medium text-muted-foreground whitespace-nowrap">${inline(c)}</th>`)
        .join('');
    const body = rows
        .map(
            (cells) =>
                `<tr class="border-t border-border/60">${cells
                    .map((c) => `<td class="px-3 py-2 whitespace-nowrap">${inline(c)}</td>`)
                    .join('')}</tr>`,
        )
        .join('');
    return `<div class="my-2 overflow-x-auto rounded-lg border border-border"><table class="w-full border-collapse text-sm"><thead class="bg-muted/70"><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;
}

export function renderRichText(text: string): string {
    const lines = text.replace(/\r\n/g, '\n').split('\n');
    const out: string[] = [];
    let list: { type: 'ul' | 'ol'; items: string[] } | null = null;

    const flushList = () => {
        if (!list) return;
        const cls = list.type === 'ul' ? 'list-disc' : 'list-decimal';
        out.push(
            `<${list.type} class="my-1 ${cls} space-y-0.5 pl-5">${list.items.map((i) => `<li>${i}</li>`).join('')}</${list.type}>`,
        );
        list = null;
    };

    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];

        // GFM pipe table: header row + separator row + body rows.
        if (isTableRow(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
            flushList();
            const header = line;
            i += 2;
            const body: string[][] = [];
            while (i < lines.length && isTableRow(lines[i])) {
                body.push(splitRow(lines[i]));
                i++;
            }
            i--; // step back; loop will increment
            out.push(renderTable(header, body));
            continue;
        }

        const bullet = line.match(/^\s*[-*]\s+(.*)$/);
        const numbered = line.match(/^\s*\d+\.\s+(.*)$/);
        const heading = line.match(/^#{1,6}\s+(.*)$/);

        if (bullet) {
            if (!list || list.type !== 'ul') {
                flushList();
                list = { type: 'ul', items: [] };
            }
            list.items.push(inline(bullet[1]));
        } else if (numbered) {
            if (!list || list.type !== 'ol') {
                flushList();
                list = { type: 'ol', items: [] };
            }
            list.items.push(inline(numbered[1]));
        } else if (heading) {
            flushList();
            out.push(`<div class="font-semibold">${inline(heading[1])}</div>`);
        } else if (line.trim() === '') {
            flushList();
        } else {
            flushList();
            out.push(`<p>${inline(line)}</p>`);
        }
    }
    flushList();

    return DOMPurify.sanitize(out.join('\n'), { ADD_ATTR: ['target', 'rel'] });
}
