<script setup lang="ts">
/**
 * Props-driven markdown display.
 *
 * Renders a tiny, dependency-free markdown subset (headings, bold,
 * italic, code, fenced code, links, lists). Output is sanitised by
 * escaping HTML in the raw source before transformation. For
 * real-world rich markdown, swap in `marked` or `markdown-it`.
 */
import { computed } from "vue"

export interface MarkdownDisplayProps {
  value?: string | number | null
  classList?: string[] | string
}

const props = defineProps<MarkdownDisplayProps>()

const content = computed<string>(() => {
  if (props.value === undefined || props.value === null) {
    return ""
  }
  return String(props.value)
})

const rootClass = computed<string>(() => {
  const cl = props.classList
  if (!cl) {
    return ""
  }
  return Array.isArray(cl) ? cl.join(" ") : cl
})

function escapeHtml(input: string): string {
  return input
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;")
}

function renderMarkdown(src: string): string {
  if (!src) {
    return ""
  }
  let text = escapeHtml(src)

  // Fenced code blocks ```...```
  text = text.replace(/```([\s\S]*?)```/g, (_, code: string) => {
    return `<pre class="bg-muted rounded p-3 overflow-auto"><code class="font-mono text-sm">${code.trim()}</code></pre>`
  })

  // Inline code `...`
  text = text.replace(/`([^`\n]+)`/g, '<code class="font-mono bg-muted px-1 rounded text-sm">$1</code>')

  // Headings (# .. ######)
  text = text.replace(/^######\s+(.+)$/gm, '<h6 class="text-base font-semibold">$1</h6>')
  text = text.replace(/^#####\s+(.+)$/gm, '<h5 class="text-lg font-semibold">$1</h5>')
  text = text.replace(/^####\s+(.+)$/gm, '<h4 class="text-xl font-semibold">$1</h4>')
  text = text.replace(/^###\s+(.+)$/gm, '<h3 class="text-2xl font-semibold">$1</h3>')
  text = text.replace(/^##\s+(.+)$/gm, '<h2 class="text-3xl font-semibold">$1</h2>')
  text = text.replace(/^#\s+(.+)$/gm, '<h1 class="text-4xl font-bold">$1</h1>')

  // Bold **text**
  text = text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
  // Italic *text*
  text = text.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1<em>$2</em>")

  // Links [text](url)
  text = text.replace(
    /\[([^\]]+)\]\(([^)\s]+)\)/g,
    '<a href="$2" class="text-primary underline" target="_blank" rel="noreferrer">$1</a>',
  )

  // Unordered list items
  text = text.replace(/^\s*[-*+]\s+(.+)$/gm, "<li>$1</li>")
  // Ordered list items
  text = text.replace(/^\s*\d+\.\s+(.+)$/gm, "<li>$1</li>")
  // Wrap consecutive <li> in <ul>
  text = text.replace(/(<li>[\s\S]*?<\/li>)(?!\s*<li>)/g, "<ul class=\"list-disc pl-6\">$1</ul>")

  // Paragraphs: any remaining double-newline blocks
  text = text
    .split(/\n{2,}/)
    .map((block) => {
      if (/^<(h\d|ul|ol|pre|blockquote|li)/.test(block.trim())) {
        return block
      }
      const trimmed = block.trim()
      return trimmed ? `<p class="leading-7">${trimmed.replace(/\n/g, "<br />")}</p>` : ""
    })
    .join("\n")

  return text
}

const html = computed<string>(() => renderMarkdown(content.value))
</script>

<template>
  <div
    class="prose prose-sm max-w-none"
    :class="rootClass"
    data-testid="markdown-display"
    v-html="html"
  />
</template>
