export function markdownToHTML(text) { // 0) Remove .../... blocks text = text.replace( /(^|\n)\s*[\s\S]*?<\/think(?:ing)?>\s*(\n\s*\n)?/gi, (_, lead) => (lead ? '\n' : '') ); // 1) Normalize line endings let tmp = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); // 2) Extract code blocks and replace with placeholders const codeblocks = []; const placeholder = idx => `@@CODEBLOCK${idx}@@`; tmp = tmp.replace(/```([^\n]*)\n([\s\S]*?)```/g, (_, lang, code) => { codeblocks.push({ lang: (lang || '').trim(), code }); return placeholder(codeblocks.length - 1); }); // 3) HTML-escape special characters let escaped = tmp .replace(/&/g, "&") .replace(//g, ">"); // 4) Headings escaped = escaped .replace(/^#### (.+)$/gm, "

$1

") .replace(/^### (.+)$/gm, "

$1

") .replace(/^## (.+)$/gm, "

$1

") .replace(/^# (.+)$/gm, "

$1

"); // 4.2) Remove blank empty lines immediately after headings escaped = escaped.replace( /(.*?<\/h[1-4]>)[ \t]*\n(?:[ \t]*\n)+/g, "$1\n" ); // 4.5) Unordered lists escaped = escaped.replace( /(^|\n)([ \t]*\* .+(?:\n[ \t]*\* .+)*)/g, (_, lead, listBlock) => { const items = listBlock .split(/\n/) .map(line => line.replace(/^[ \t]*\*\s+/, '').trim()) .map(item => `
  • ${item}
  • `) .join(''); return `${lead}`; } ); // 4.6) Markdown tables (GitHub-style). Strict: requires header, separator, ≥2 cols. const mdTableBlockRe = /(^\|[^\n]*\|?\s*\n\|\s*[:\-]+(?:\s*\|\s*[:\-]+)+\s*\|?\s*\n(?:\|[^\n]*\|?\s*(?:\n|$))*)/gm; escaped = escaped.replace(mdTableBlockRe, (block) => { // Preserve trailing newline so '^---$' can match next line const hadTrailingNewline = /\n$/.test(block); const lines = block.replace(/\n$/, '').split('\n'); const split = (line) => line.replace(/^\||\|$/g, '').split('|').map(s => s.trim()); const headers = split(lines[0]); const seps = split(lines[1]); if (headers.length < 2 || seps.length < 2) return block; if (!seps.every(s => /^[ :\-]+$/.test(s) && /-/.test(s))) return block; // Alignment per column (default left). Vertical-align top by default. const aligns = seps.map(seg => { const s = seg.replace(/\s+/g,''); const left = s.startsWith(':'); const right = s.endsWith(':'); if (left && right) return 'center'; if (right) return 'right'; return 'left'; }); const bodyLines = lines.slice(2).filter(l => /^\|/.test(l.trim())); const cellStyle = (i) => ` style="text-align:${aligns[i] || 'left'};vertical-align:top;border:1px solid #e5e7eb;padding:.6rem .75rem"`; const ths = headers.map((h,i)=>`${h}`).join(''); const rows = bodyLines.map(line => { const cells = split(line); const tds = cells.map((c,i)=>`${c}`).join(''); return `${tds}`; }).join(''); // Wrapper: rounded outer border + top/bottom spacing; inner cells keep their own borders. const wrapperOpen = `
    `; const table = `${ths}${rows}
    `; return wrapperOpen + table + (hadTrailingNewline ? '\n' : ''); }); // 4.75) Horizontal rules escaped = escaped.replace(/^---\s*$/gm, "
    "); // 5) Bold, italic, inline code let html = escaped .replace(/\*\*(.+?)\*\*/g, "$1") .replace(/(?$1") .replace(/`(.+?)`/g, "$1"); // 5.5) Links html = html.replace(/\[([^\]]+?)\]\(([^)]+?)\)/g, '$1 $2'); // 6) Restore code blocks with title bar (language) html = html.replace(/@@CODEBLOCK(\d+)@@/g, (_, idx) => { const { lang, code } = codeblocks[+idx]; const title = (lang && lang.trim()) ? lang.trim() : 'code'; const escapedCode = code.replace(//g, ">"); const head = `
    ${title}
    `; const body = `
    ${escapedCode}
    `; return `
    ${head}${body}
    `; }); // 7) Convert line-breaks to
    html = html.replace(/\n/g, "
    "); // 8) Cleanup stray
    around lists/tables/wrappers html = html .replace(/
    \s*(
      )/g, "$1") .replace(/(<\/ul>)\s*
      /g, "$1") .replace(/
      \s*(
      ]*>)/g, "$1") .replace(/(<\/div>)\s*
      /g, "$1") .replace(/
      \s*(
      ]*>)/g, "$1") .replace(/(<\/div>)\s*
      /g, "$1") .replace(/
      \s*(]*>)/g, "$1") .replace(/(<\/table>)\s*
      /g, "$1"); // 9) Trim spaces/tabs and remove empty newline(s) immediately after
      html = html .replace(/(
      )[ \t]+/g, "$1") // remove spaces/tabs .replace(/(
      )(?:[ \t]*
      )+/g, "$1"); // remove one or more blank lines (now
      ) after
      return html; }