export function renderAssistantMarkdown(md, sources, options) {
  var opts = options || {};
  var codeBlocks = [];
  var text = String(md || '').replace(/```(\w+)?\n([\s\S]*?)```/g, function (_, lang, code) {
    var idx = codeBlocks.length;
    codeBlocks.push({ lang: (lang || '').toLowerCase(), code: code });
    return '\n@@CODEBLOCK_' + idx + '@@\n';
  });
  text = stripOrphanMarkdownMarkers(normalizeMarkdownText(text));
  text = renderLatexText(text, opts.katex);
  text = normalizeAdjacentCitationClusters(text, sources || []);

  var html;
  if (opts.marked && typeof opts.marked.parse === 'function') {
    html = opts.marked.parse(text, { breaks: true, gfm: true });
  } else if (opts.markdownIt && typeof opts.markdownIt.render === 'function') {
    html = opts.markdownIt.render(text);
  } else {
    html = fallbackMarkdown(text);
  }

  html = renderCitationLinks(html, sources || [], opts);
  html = html.replace(/@@CODEBLOCK_(\d+)@@/g, function (_, idx) {
    var block = codeBlocks[Number(idx)] || { lang: '', code: '' };
    if (block.lang === 'mermaid') return '<div class="assistant-mermaid" data-mermaid="' + escapeAttr(block.code) + '">Rendering graph...</div>';
    if (block.lang === 'chart' || block.lang === 'chartjs') return '<canvas class="assistant-chart" data-chart="' + escapeAttr(block.code) + '"></canvas>';
    return '<pre><code>' + escapeHtml(block.code) + '</code></pre>';
  });
  html = wrapTables(html);

  return typeof opts.sanitize === 'function' ? opts.sanitize(html) : html;
}

function wrapTables(html) {
  return String(html || '')
    .replace(/<table(\s[^>]*)?>/g, '<div class="assistant-table-scroll"><table$1>')
    .replace(/<\/table>/g, '</table></div>');
}

export function renderCitationLinks(html, sources, options) {
  var opts = options || {};
  return String(html || '').replace(/\[((?:\d+\s*,\s*)*\d+)\]/g, function (match, cluster) {
    var nums = cluster.split(',').map(function (n) { return Number(n.trim()); }).filter(function (n) { return Number.isInteger(n) && n > 0; });
    if (!nums.length || nums.some(function (n) { return !sources[n - 1]; })) return match;
    return nums.map(function (n) {
      var source = sources[n - 1];
      var title = source ? source.title || source.resource || 'Source' : 'Source';
      var page = source && (source.page || source.page_number || source.pageNumber);
      var label = 'Source ' + n + ': ' + title + (page ? ', page ' + page : '');
      var text = opts.citationLabel === 'number' ? String(n) : 'src';
      return '<a class="assistant-cite" href="#assistant-source-' + n + '" data-source-number="' + n + '" title="' + escapeHtml(label) + '" aria-label="' + escapeAttr(label) + '">' + text + '</a>';
    }).join(' ');
  });
}

export function normalizeAdjacentCitationClusters(text, sources) {
  var available = Array.isArray(sources) ? sources : [];
  var normalized = String(text || '').replace(/((?:\[(?:\d+\s*,\s*)*\d+\]\s*)+)\[((?:\d+\s*,\s*)*\d+)(?=$|[.,;:])/g, function(match, completeClusters, trailingCluster) {
    var nums = citationNumbers(completeClusters).concat(parseCitationCluster(trailingCluster));
    if (!allCitationsAvailable(nums, available)) return match;
    return formatCitationCluster(nums);
  });

  return normalized.replace(/(?:\[(?:\d+\s*,\s*)*\d+\]\s*){2,}/g, function(match) {
    var nums = citationNumbers(match);
    if (!allCitationsAvailable(nums, available)) return match;
    return formatCitationCluster(nums);
  });
}

function citationNumbers(text) {
  var clusters = String(text || '').match(/\[((?:\d+\s*,\s*)*\d+)\]/g) || [];
  var nums = [];
  clusters.forEach(function(cluster) {
    nums = nums.concat(parseCitationCluster(cluster.slice(1, -1)));
  });
  return nums;
}

function parseCitationCluster(cluster) {
  return String(cluster || '').split(',').map(function(n) { return Number(n.trim()); }).filter(function(n) { return Number.isInteger(n) && n > 0; });
}

function allCitationsAvailable(nums, sources) {
  return nums.length > 0 && nums.every(function(n) { return sources[n - 1]; });
}

function formatCitationCluster(nums) {
  nums = Array.from(new Set(nums)).sort(function(a, b) { return a - b; });
  return '[' + nums.join(', ') + ']';
}

export function normalizeMarkdownText(text) {
  return stripOrphanMarkdownMarkers(normalizeTableSourceCitationCells(String(text || '')
    .replace(/\r\n/g, '\n')
    .replace(/(\[(?:\d+\s*,\s*)*\d+\])\s*[-–—]\s*/g, '$1\n- ')
    .replace(/([.!?])\s*[-–—]\s+(\*\*)?/g, '$1\n- $2')
    .replace(/(:)\s*[-–—]\s+(\*\*)?/g, '$1\n- $2')
    .replace(/(\[(?:\d+\s*,\s*)*\d+\]\.)\s+(\d+\.\s+[A-Z][A-Za-z][^\n]{0,80})/g, '$1\n$2')
    .replace(/([.!?])\s+(\d+\.\s+[A-Z][A-Za-z][^\n]{0,80})/g, '$1\n$2')
    .replace(/(\[(?:\d+\s*,\s*)*\d+\])(?=\s*(?:[A-Z][A-Za-z]+\s+){1,4}(?:deficits?|distress|apnoea|apnea|vomiting|seizures?|signs?|symptoms?|criteria|indications?|risk|oxygen|saturation|dehydration|lethargy|toxicity)\b)/g, '$1\n')
    .replace(/([^\n])\s+(#{1,4}\s+)/g, '$1\n\n$2')
    .replace(/(#{1,4}\s+[^\n]+?)\s+(-\s+)/g, '$1\n\n$2')
    .replace(/(#{1,4}\s+[^\n]+)\n(-\s+)/g, '$1\n\n$2')
    .replace(/([^\n])\s+(-\s+(?:Mainstay|Medications|Hospitalization|Other therapies|Prevention|Short-acting|Anticholinergics|Systemic|Adjuncts|Long-term|Infants|Differentiating|Persistent|Severe|Need for|Inadequate)\b)/g, '$1\n$2')
    .replace(/([^\n])\s+(-\s+[^\n])/g, '$1\n$2')
    .trim()));
}

export function normalizeTableSourceCitationCells(text) {
  var lines = String(text || '').split('\n');
  for (var i = 0; i < lines.length - 1; i++) {
    if (!/^\s*\|.*\|\s*$/.test(lines[i]) || !/^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(lines[i + 1])) continue;
    var header = tableCells(lines[i]);
    var sourceCols = [];
    header.forEach(function(cell, idx) {
      if (/^(?:source|sources|source\(s\)|citation|citations|citation\(s\)|reference|references|ref|refs)$/i.test(cell.trim())) sourceCols.push(idx);
    });
    if (!sourceCols.length) continue;
    var j = i + 2;
    while (j < lines.length && /^\s*\|.*\|\s*$/.test(lines[j])) {
      lines[j] = rewriteTableCells(lines[j], sourceCols, function(cell) {
        return normalizeBareCitationCell(cell);
      });
      j++;
    }
    i = j - 1;
  }
  return lines.join('\n');
}

function rewriteTableCells(line, indexes, fn) {
  var trimmed = String(line || '').trim();
  var leading = /^\|/.test(trimmed);
  var trailing = /\|$/.test(trimmed);
  var cells = tableCells(line);
  indexes.forEach(function(idx) {
    if (idx < cells.length) cells[idx] = fn(cells[idx]);
  });
  return (leading ? '| ' : '') + cells.join(' | ') + (trailing ? ' |' : '');
}

function normalizeBareCitationCell(cell) {
  var text = String(cell || '').trim();
  if (/^\[(?:\d+\s*,\s*)*\d+\]$/.test(text)) return text;
  if (/^\d+(?:\s*,\s*\d+)*$/.test(text)) return '[' + text.replace(/\s*,\s*/g, ', ') + ']';
  return text.replace(/(^|\s)(\d+(?:\s*,\s*\d+)+)(?=$|\s)/g, function(match, prefix, nums) {
    return prefix + '[' + nums.replace(/\s*,\s*/g, ', ') + ']';
  });
}

export function stripOrphanMarkdownMarkers(text) {
  return String(text || '')
    .replace(/\s*(?:\*\*|__|\*|_)\s*$/g, '')
    .replace(/\s*(?:\*\*|__)?\s*(?:Figure|Fig\.)\s*(?:\*\*|__)?\s*$/i, '')
    .trim();
}

export function fallbackMarkdown(text) {
  var html = escapeHtml(text)
    .replace(/^### (.*)$/gm, '<h3>$1</h3>')
    .replace(/^## (.*)$/gm, '<h2>$1</h2>')
    .replace(/^# (.*)$/gm, '<h1>$1</h1>')
    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
    .replace(/\*(.*?)\*/g, '<em>$1</em>')
    .replace(/`([^`]+)`/g, '<code>$1</code>');
  html = html.split(/\n{2,}/).map(function (block) {
    if (/^\s*<h\d/.test(block) && block.indexOf('\n') !== -1) {
      var parts = block.split('\n');
      return parts.shift() + fallbackMarkdown(parts.join('\n'));
    }
    if (/^\s*<(h\d|ul|ol|pre|div)/.test(block)) return block;
    var lines = block.split('\n');
    if (isMarkdownTable(lines)) return renderFallbackTable(lines);
    if (lines.some(function (l) { return /^\s*[-*] /.test(l); })) return renderMixedList(lines);
    if (lines.every(function (l) { return /^\s*[-*] /.test(l) || !l.trim(); })) {
      return '<ul>' + lines.filter(Boolean).map(function (l) { return '<li>' + l.replace(/^\s*[-*] /, '') + '</li>'; }).join('') + '</ul>';
    }
    if (lines.every(function (l) { return /^\s*\d+\. /.test(l) || !l.trim(); })) {
      return '<ol>' + lines.filter(Boolean).map(function (l) { return '<li>' + l.replace(/^\s*\d+\. /, '') + '</li>'; }).join('') + '</ol>';
    }
    return '<p>' + block.replace(/\n/g, '<br>') + '</p>';
  }).join('');
  return html;
}

function renderMixedList(lines) {
  var html = '';
  var list = [];
  var paragraph = [];
  lines.forEach(function(line) {
    if (/^\s*[-*] /.test(line)) {
      if (paragraph.length) {
        html += '<p>' + paragraph.join('<br>') + '</p>';
        paragraph = [];
      }
      list.push(line.replace(/^\s*[-*] /, ''));
      return;
    }
    if (list.length) {
      html += '<ul>' + list.map(function(item) { return '<li>' + item + '</li>'; }).join('') + '</ul>';
      list = [];
    }
    if (line.trim()) paragraph.push(line);
  });
  if (paragraph.length) html += '<p>' + paragraph.join('<br>') + '</p>';
  if (list.length) html += '<ul>' + list.map(function(item) { return '<li>' + item + '</li>'; }).join('') + '</ul>';
  return html;
}

function isMarkdownTable(lines) {
  return lines.length >= 2 && /^\s*\|.*\|\s*$/.test(lines[0]) && /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(lines[1]);
}

function renderFallbackTable(lines) {
  var header = tableCells(lines[0]);
  var rows = lines.slice(2).filter(function(line) { return /^\s*\|.*\|\s*$/.test(line); }).map(tableCells);
  return '<table><thead><tr>' + header.map(function(cell) { return '<th>' + cell + '</th>'; }).join('') + '</tr></thead><tbody>' +
    rows.map(function(row) { return '<tr>' + row.map(function(cell) { return '<td>' + cell + '</td>'; }).join('') + '</tr>'; }).join('') +
    '</tbody></table>';
}

function tableCells(line) {
  return String(line || '').trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(function(cell) { return cell.trim(); });
}

export function renderLatexText(text, katex) {
  if (!katex) return text;
  return String(text || '')
    .replace(/\$\$([\s\S]+?)\$\$/g, function(_, expr) { return safeKatex(katex, expr, true); })
    .replace(/\\\[([\s\S]+?)\\\]/g, function(_, expr) { return safeKatex(katex, expr, true); })
    .replace(/\$([^$\n]+?)\$/g, function(_, expr) { return safeKatex(katex, expr, false); })
    .replace(/\\\((.+?)\\\)/g, function(_, expr) { return safeKatex(katex, expr, false); });
}

function safeKatex(katex, expr, displayMode) {
  try { return katex.renderToString(expr, { displayMode: displayMode, throwOnError: false }); }
  catch (e) { return escapeHtml(expr); }
}

export function escapeHtml(s) {
  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

export function escapeAttr(s) {
  return escapeHtml(s).replace(/'/g, '&#39;');
}
