<template>
  <div class="markdown-wrapper">
    <div v-html="renderedHtml" class="max-w-none markdown-content"></div>
  </div>
</template>

<script setup lang="ts">
import { ref, watch, nextTick, onMounted } from 'vue'
import { marked } from 'marked'
import DOMPurify from 'dompurify'
import Prism from 'prismjs'
import 'prismjs/themes/prism.css'
import 'prismjs/themes/prism-okaidia.css'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-typescript'

interface MarkedCode {
  text: string
  lang?: string
  escaped?: boolean
}

const props = defineProps<{ content: string }>()
const renderedHtml = ref('')

const renderer = new marked.Renderer()

renderer.code = function ({ text, lang }: MarkedCode): string {
  const language = lang && Prism.languages[lang] ? lang : 'markup'
  const highlighted = Prism.highlight(text, Prism.languages[language], language)

  return `
    <div class="code-block relative overflow-visible my-4">
      <div class="code-block-header sticky top-0 z-10 flex items-center justify-end
                  rounded-tl-md rounded-tr-md px-2 py-1 bg-transparent text-xs font-medium text-gray-300">
        <button class="copy-btn text-xs px-2 py-0.5 rounded border bg-gray-700 hover:bg-gray-600 transition-all">
          Copy
        </button>
      </div>
      <div class="-mt-10"><pre class="code-block-body whitespace-pre-wrap break-words p-3 bg-gray-100 rounded-md language-${language}" tabindex="0"><span class="text-xs capitalize mt-[-10px] absolute left-[6px]">${language}</span><code class="w-full language-${language}">
${highlighted}
</code></pre>
      </div>
    </div>
  `
}

renderer.link = function ({ href, title, tokens }) {
  const text = marked.Parser.parseInline(tokens)
  const titleAttr = title ? ` title="${title}"` : ''
  return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:underline">${text}</a>`
}

marked.setOptions({
  renderer,
  breaks: true,
  gfm: true,
})

watch(
  () => props.content,
  async (newContent) => {
    const result = marked.parse(newContent || '')
    const rawHtml: string = typeof result === 'string' ? result : await result

    // Sanitize the generated HTML
    const sanitized = DOMPurify.sanitize(rawHtml, { ADD_ATTR: ['target'] }) as string
    renderedHtml.value = sanitized

    await nextTick()
    Prism.highlightAll()
    attachCopyButtons()
  },
  { immediate: true }
)

function attachCopyButtons() {
  const copyButtons = document.querySelectorAll('.code-block .copy-btn')
  copyButtons.forEach((button) => {
    button.removeEventListener('click', onCopyClick)
    button.addEventListener('click', onCopyClick)
  })
}

function onCopyClick(this: HTMLElement) {
  const block = this.closest('.code-block')
  if (!block) {
    console.warn('No .code-block found for this copy button.')
    return
  }

  const codeElem = block.querySelector('code')
  if (!codeElem) {
    console.warn('No <code> element found in .code-block.')
    return
  }

  const textToCopy = codeElem.textContent || ''

  navigator.clipboard
    .writeText(textToCopy)
    .then(() => {
      this.textContent = 'Copied!'
      setTimeout(() => {
        this.textContent = 'Copy'
      }, 1500)
    })
    .catch((err) => {
      console.error('Failed to copy text:', err)
    })
}

onMounted(() => {
  Prism.highlightAll()
})
</script>

<style scoped>
:deep(.prose) {
  white-space: normal;
}

.markdown-wrapper {
  overflow-wrap: break-word;
  word-wrap: break-word;
  word-break: break-word;
}
.markdown-content {
  width: fit-content;
  overflow-wrap: break-word;
  word-wrap: break-word;
}

:deep(.markdown-content *) {
  overflow-wrap: break-word;
  word-wrap: break-word;
  word-break: break-word;
}
</style>
