// markdown.jsx
// Markdown rendering + line-level diff utilities.
// Exposes globals: renderMarkdown(md), diffLines(a, b)

(function () {
  function escapeHtml(s) {
    return s
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function renderInline(s) {
    // Code spans first so we don't process **/* inside them
    const tokens = [];
    s = s.replace(/`([^`]+)`/g, (_, c) => {
      tokens.push('<code>' + escapeHtml(c) + '</code>');
      return '\u0001' + (tokens.length - 1) + '\u0001';
    });

    let out = escapeHtml(s);

    // Links [text](url)
    out = out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, t, u) =>
      `<a href="${u}" target="_blank" rel="noopener">${t}</a>`
    );
    // Bold
    out = out.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
    out = out.replace(/__([^_\n]+)__/g, '<strong>$1</strong>');
    // Italic
    out = out.replace(/(^|[^\*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
    out = out.replace(/(^|[^_])_([^_\n]+)_/g, '$1<em>$2</em>');
    // Strikethrough
    out = out.replace(/~~([^~]+)~~/g, '<del>$1</del>');

    // Restore code spans
    out = out.replace(/\u0001(\d+)\u0001/g, (_, i) => tokens[+i]);
    return out;
  }

  function renderMarkdown(md) {
    if (!md) return '';
    const lines = md.split('\n');
    let html = '';
    let i = 0;
    let inUl = false;
    let inOl = false;
    let inBq = false;
    let listIndent = 0;

    function closeLists() {
      while (inUl || inOl) {
        html += inUl ? '</ul>' : '</ol>';
        inUl = inOl = false;
      }
    }
    function closeBq() {
      if (inBq) { html += '</blockquote>'; inBq = false; }
    }

    while (i < lines.length) {
      const line = lines[i];
      const trimmed = line.trim();

      // Fenced code block
      const fence = trimmed.match(/^```(\w*)\s*$/);
      if (fence) {
        closeLists(); closeBq();
        const lang = fence[1] || '';
        let body = '';
        i++;
        while (i < lines.length && !/^```\s*$/.test(lines[i].trim())) {
          body += lines[i] + '\n';
          i++;
        }
        i++; // consume closing fence
        html += `<pre><code class="lang-${lang}">${escapeHtml(body.replace(/\n$/, ''))}</code></pre>`;
        continue;
      }

      // ATX heading
      const h = trimmed.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
      if (h) {
        closeLists(); closeBq();
        const level = h[1].length;
        html += `<h${level}>${renderInline(h[2])}</h${level}>`;
        i++;
        continue;
      }

      // Horizontal rule
      if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
        closeLists(); closeBq();
        html += '<hr/>';
        i++;
        continue;
      }

      // Blockquote
      if (/^>\s?/.test(trimmed)) {
        closeLists();
        if (!inBq) { html += '<blockquote>'; inBq = true; }
        const content = trimmed.replace(/^>\s?/, '');
        html += `<p>${renderInline(content)}</p>`;
        i++;
        continue;
      } else if (inBq && trimmed === '') {
        // blank line inside bq context — close it
        closeBq();
      }

      // Unordered list item
      const ul = line.match(/^(\s*)[-*+]\s+(.+)$/);
      if (ul) {
        closeBq();
        if (!inUl) { closeLists(); html += '<ul>'; inUl = true; }
        // Task list?
        let item = ul[2];
        const task = item.match(/^\[([ xX])\]\s+(.*)$/);
        if (task) {
          const checked = task[1].toLowerCase() === 'x' ? ' checked' : '';
          html += `<li><input type="checkbox" disabled${checked}/>${renderInline(task[2])}</li>`;
        } else {
          html += `<li>${renderInline(item)}</li>`;
        }
        i++;
        continue;
      }

      // Ordered list item
      const ol = line.match(/^(\s*)\d+\.\s+(.+)$/);
      if (ol) {
        closeBq();
        if (!inOl) { closeLists(); html += '<ol>'; inOl = true; }
        html += `<li>${renderInline(ol[2])}</li>`;
        i++;
        continue;
      }

      // Table — header row followed by separator
      if (/^\|.+\|$/.test(trimmed) && i + 1 < lines.length && /^\|[\s\-:|]+\|$/.test(lines[i + 1].trim())) {
        closeLists(); closeBq();
        const headerCells = trimmed.slice(1, -1).split('|').map(c => c.trim());
        i += 2;
        let tbl = '<table><thead><tr>';
        headerCells.forEach(c => { tbl += `<th>${renderInline(c)}</th>`; });
        tbl += '</tr></thead><tbody>';
        while (i < lines.length && /^\|.+\|$/.test(lines[i].trim())) {
          const cells = lines[i].trim().slice(1, -1).split('|').map(c => c.trim());
          tbl += '<tr>' + cells.map(c => `<td>${renderInline(c)}</td>`).join('') + '</tr>';
          i++;
        }
        tbl += '</tbody></table>';
        html += tbl;
        continue;
      }

      // Blank line
      if (trimmed === '') {
        closeLists();
        closeBq();
        i++;
        continue;
      }

      // Paragraph — gather contiguous non-empty non-block lines
      closeLists(); closeBq();
      let para = trimmed;
      i++;
      while (
        i < lines.length &&
        lines[i].trim() !== '' &&
        !/^(#{1,6})\s+/.test(lines[i].trim()) &&
        !/^(-{3,}|\*{3,}|_{3,})$/.test(lines[i].trim()) &&
        !/^>\s?/.test(lines[i].trim()) &&
        !/^(\s*)[-*+]\s+/.test(lines[i]) &&
        !/^(\s*)\d+\.\s+/.test(lines[i]) &&
        !/^```/.test(lines[i].trim())
      ) {
        para += '\n' + lines[i].trim();
        i++;
      }
      // Single-line breaks inside paragraph -> <br>
      const inner = para.split('\n').map(renderInline).join('<br/>');
      html += `<p>${inner}</p>`;
    }
    closeLists();
    closeBq();
    return html;
  }

  // ─── Line-level diff via LCS ──────────────────────────────
  function diffLines(a, b) {
    const A = (a || '').split('\n');
    const B = (b || '').split('\n');
    const m = A.length, n = B.length;
    // Trim common prefix/suffix to speed up
    let pre = 0;
    while (pre < m && pre < n && A[pre] === B[pre]) pre++;
    let suf = 0;
    while (suf < m - pre && suf < n - pre && A[m - 1 - suf] === B[n - 1 - suf]) suf++;

    const aMid = A.slice(pre, m - suf);
    const bMid = B.slice(pre, n - suf);
    const mm = aMid.length, nn = bMid.length;

    // LCS table
    const dp = Array.from({ length: mm + 1 }, () => new Uint32Array(nn + 1));
    for (let i = 1; i <= mm; i++) {
      for (let j = 1; j <= nn; j++) {
        if (aMid[i - 1] === bMid[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
        else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
    const mid = [];
    let i = mm, j = nn;
    while (i > 0 || j > 0) {
      if (i > 0 && j > 0 && aMid[i - 1] === bMid[j - 1]) {
        mid.unshift({ type: 'eq', a: aMid[i - 1], b: bMid[j - 1] }); i--; j--;
      } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
        mid.unshift({ type: 'add', b: bMid[j - 1] }); j--;
      } else {
        mid.unshift({ type: 'del', a: aMid[i - 1] }); i--;
      }
    }
    const out = [];
    for (let k = 0; k < pre; k++) out.push({ type: 'eq', a: A[k], b: B[k] });
    for (const r of mid) out.push(r);
    for (let k = 0; k < suf; k++) out.push({ type: 'eq', a: A[m - suf + k], b: B[n - suf + k] });

    // Now align left/right columns: each row should align eq->eq, but add/del pair up when adjacent.
    // We'll convert sequential dels followed by adds into side-by-side change rows.
    const rows = [];
    let p = 0;
    while (p < out.length) {
      if (out[p].type === 'eq') {
        rows.push({ left: out[p].a, right: out[p].b, type: 'eq' });
        p++;
      } else {
        // Collect runs of del then add
        const dels = [];
        while (p < out.length && out[p].type === 'del') { dels.push(out[p].a); p++; }
        const adds = [];
        while (p < out.length && out[p].type === 'add') { adds.push(out[p].b); p++; }
        const maxLen = Math.max(dels.length, adds.length);
        for (let k = 0; k < maxLen; k++) {
          const l = k < dels.length ? dels[k] : null;
          const r = k < adds.length ? adds[k] : null;
          if (l !== null && r !== null) rows.push({ left: l, right: r, type: 'chg' });
          else if (l !== null) rows.push({ left: l, right: null, type: 'del' });
          else rows.push({ left: null, right: r, type: 'add' });
        }
      }
    }

    // Stats
    let added = 0, removed = 0;
    for (const r of rows) {
      if (r.type === 'add') added++;
      else if (r.type === 'del') removed++;
      else if (r.type === 'chg') { added++; removed++; }
    }
    return { rows, added, removed };
  }

  window.renderMarkdown = renderMarkdown;
  window.diffLines = diffLines;
})();
