const escapeMap = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;'
}

function escapeHtml(value) {
  return String(value ?? '').replace(/[&<>"']/g, (char) => escapeMap[char])
}

function renderInline(value) {
  let html = escapeHtml(value)
  html = html.replace(/`([^`]+)`/g, '<code>$1</code>')
  html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
  html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
  return html
}

function renderTable(rows) {
  if (rows.length < 2) return ''
  const headers = splitTableRow(rows[0])
  const bodyRows = rows.slice(2).map(splitTableRow)
  const headerHtml = headers.map((header) => `<th>${renderInline(header)}</th>`).join('')
  const bodyHtml = bodyRows
    .map((row) => `<tr>${row.map((cell) => `<td>${renderInline(cell)}</td>`).join('')}</tr>`)
    .join('')

  return `<table><thead><tr>${headerHtml}</tr></thead><tbody>${bodyHtml}</tbody></table>`
}

function splitTableRow(row) {
  return row
    .trim()
    .replace(/^\|/, '')
    .replace(/\|$/, '')
    .split('|')
    .map((cell) => cell.trim())
}

function isTableDivider(line) {
  return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line)
}

function flushParagraph(parts, output) {
  if (!parts.length) return
  output.push(`<p>${renderInline(parts.join(' '))}</p>`)
  parts.length = 0
}

function flushList(items, output) {
  if (!items.length) return
  output.push(`<ul>${items.map((item) => `<li>${renderInline(item)}</li>`).join('')}</ul>`)
  items.length = 0
}

function flushTable(rows, output) {
  if (!rows.length) return
  const html = renderTable(rows)
  if (html) {
    output.push(html)
  }
  rows.length = 0
}

export function renderMarkdown(markdown) {
  const lines = String(markdown || '').replace(/\r\n/g, '\n').split('\n')
  const output = []
  const paragraph = []
  const listItems = []
  const tableRows = []
  let codeLanguage = ''
  let codeLines = []

  function flushBlocks() {
    flushParagraph(paragraph, output)
    flushList(listItems, output)
    flushTable(tableRows, output)
  }

  lines.forEach((line) => {
    const fenceMatch = line.match(/^```(\w+)?\s*$/)
    if (fenceMatch) {
      if (codeLanguage || codeLines.length) {
        const languageClass = codeLanguage ? ` class="language-${escapeHtml(codeLanguage)}"` : ''
        output.push(`<pre><code${languageClass}>${escapeHtml(`${codeLines.join('\n')}\n`)}</code></pre>`)
        codeLanguage = ''
        codeLines = []
      } else {
        flushBlocks()
        codeLanguage = fenceMatch[1] || 'text'
      }
      return
    }

    if (codeLanguage || codeLines.length) {
      codeLines.push(line)
      return
    }

    if (!line.trim()) {
      flushBlocks()
      return
    }

    if (tableRows.length || (line.includes('|') && lines[lines.indexOf(line) + 1] && isTableDivider(lines[lines.indexOf(line) + 1]))) {
      flushParagraph(paragraph, output)
      flushList(listItems, output)
      tableRows.push(line)
      return
    }

    const headingMatch = line.match(/^(#{1,4})\s+(.+)$/)
    if (headingMatch) {
      flushBlocks()
      const level = headingMatch[1].length
      output.push(`<h${level}>${renderInline(headingMatch[2])}</h${level}>`)
      return
    }

    const listMatch = line.match(/^\s*[-*]\s+(.+)$/)
    if (listMatch) {
      flushParagraph(paragraph, output)
      listItems.push(listMatch[1])
      return
    }

    const quoteMatch = line.match(/^>\s+(.+)$/)
    if (quoteMatch) {
      flushBlocks()
      output.push(`<blockquote>${renderInline(quoteMatch[1])}</blockquote>`)
      return
    }

    flushList(listItems, output)
    flushTable(tableRows, output)
    paragraph.push(line.trim())
  })

  if (codeLanguage || codeLines.length) {
    const languageClass = codeLanguage ? ` class="language-${escapeHtml(codeLanguage)}"` : ''
    output.push(`<pre><code${languageClass}>${escapeHtml(`${codeLines.join('\n')}\n`)}</code></pre>`)
  }
  flushBlocks()

  return output.join('\n')
}
