<template>
  <div class="markdown-renderer">
    <template v-if="content">
      <div class="content-toolbar render-actions">
        <div class="render-actions-left">
          <el-segmented
            v-if="mode === 'markdown'"
            v-model="activeFlavor"
            :options="flavorOptions"
            size="small"
          />
          <el-segmented
            v-else-if="mode === 'richtext'"
            v-model="activeRichtextFormat"
            :options="richtextOptions"
            size="small"
          />
        </div>
        <div class="render-actions-right">
          <el-button type="primary" size="small" class="action-button primary" @click="handleDownload()">
            <template #icon>
              <el-icon><Download /></el-icon>
            </template>
            下载
          </el-button>
        </div>
      </div>
      <div class="render-content">
        <div v-if="mode === 'markdown'" class="render-shell">
          <div ref="markdownRef" class="markdown-content rendered-html" v-html="renderedContent"></div>
        </div>

        <div v-else-if="mode === 'html'" class="render-shell">
          <div ref="htmlRef" class="markdown-content rendered-html" v-html="renderedContent"></div>
        </div>

        <div v-else-if="mode === 'pdf'" class="render-shell">
          <div class="pdf-stage">
            <div ref="pdfRef" class="pdf-preview-pages">
              <div
                v-for="(page, index) in pdfPages"
                :key="`pdf-page-${index}`"
                class="pdf-page"
              >
                <div class="markdown-content pdf-content" v-html="page"></div>
              </div>
            </div>
          </div>
          <div
            ref="pdfMeasureRef"
            class="pdf-measure markdown-content pdf-content"
            v-html="renderedContent"
          ></div>
        </div>

        <div v-else class="render-shell">
          <div ref="richtextRef" class="markdown-content rendered-html richtext-content" v-html="renderedContent"></div>
        </div>
      </div>
    </template>

    <div v-else class="empty-state">
      <el-empty :description="$t('results.noResults')" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import MarkdownIt from 'markdown-it'
import type { PropType } from 'vue'
import { Download } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { loadHtml2Pdf } from '@/utils/wordToPdf'

type RenderMode = 'markdown' | 'html' | 'pdf' | 'richtext'
type FlavorMode = 'commonmark' | 'gfm'
type RichtextFormat = 'word' | 'rtf'

const props = defineProps({
  content: {
    type: String as PropType<string>,
    default: ''
  },
  mode: {
    type: String as PropType<RenderMode>,
    default: 'markdown'
  },
  flavor: {
    type: String as PropType<FlavorMode>,
    default: 'gfm'
  }
})

const activeFlavor = ref<FlavorMode>(props.flavor)
const activeRichtextFormat = ref<RichtextFormat>('word')
const markdownRef = ref<HTMLElement | null>(null)
const htmlRef = ref<HTMLElement | null>(null)
const pdfRef = ref<HTMLElement | null>(null)
const pdfMeasureRef = ref<HTMLElement | null>(null)
const richtextRef = ref<HTMLElement | null>(null)
const pdfPages = ref<string[]>([])

watch(
  () => props.flavor,
  (value) => {
    activeFlavor.value = value
  }
)

const flavorOptions = [
  { label: 'CommonMark', value: 'commonmark' },
  { label: 'GFM', value: 'gfm' }
]

const richtextOptions = [
  { label: 'Word', value: 'word' },
  { label: 'RTF', value: 'rtf' }
]

function applyMathSupport(md: MarkdownIt) {
  md.use(function (md) {
    md.inline.ruler.after('escape', 'math_inline', function (state, silent) {
      const start = state.pos
      if (state.src.charCodeAt(start) !== 0x24) return false
      if (state.src.charCodeAt(start + 1) !== 0x24) return false

      let pos = start + 2
      let found = false
      while (pos < state.posMax) {
        if (state.src.charCodeAt(pos) === 0x24 && state.src.charCodeAt(pos + 1) === 0x24) {
          found = true
          break
        }
        pos++
      }
      if (!found) return false

      if (!silent) {
        const token = state.push('math_inline', 'span', 0)
        token.content = state.src.slice(start + 2, pos)
        token.markup = '$$'
      }

      state.pos = pos + 2
      return true
    })

    md.inline.ruler.after('escape', 'math_inline_single', function (state, silent) {
      const start = state.pos
      if (state.src.charCodeAt(start) !== 0x24) return false
      if (state.src.charCodeAt(start + 1) === 0x24) return false

      let pos = start + 1
      let found = false
      while (pos < state.posMax) {
        if (state.src.charCodeAt(pos) === 0x24) {
          found = true
          break
        }
        pos++
      }
      if (!found) return false

      if (!silent) {
        const token = state.push('math_inline_single', 'span', 0)
        token.content = state.src.slice(start + 1, pos)
        token.markup = '$'
      }

      state.pos = pos + 1
      return true
    })
  })

  md.renderer.rules.math_inline = function(tokens, idx) {
    return `<span class="math-inline">$$${tokens[idx].content}$$</span>`
  }

  md.renderer.rules.math_inline_single = function(tokens, idx) {
    return `<span class="math-inline">$${tokens[idx].content}$</span>`
  }
}

function createMarkdownRenderer(flavor: FlavorMode) {
  const md = flavor === 'commonmark'
    ? new MarkdownIt('commonmark', {
        html: true,
        linkify: true,
        typographer: true,
        breaks: false
      })
    : new MarkdownIt({
        html: true,
        linkify: true,
        typographer: true,
        breaks: true
      })

  applyMathSupport(md)
  return md
}

const renderedContent = computed(() => {
  if (!props.content) return ''
  const flavor = props.mode === 'markdown' ? activeFlavor.value : 'gfm'
  const renderer = createMarkdownRenderer(flavor)
  return renderer.render(props.content)
})

async function recalculatePdfPages() {
  if (props.mode !== 'pdf') {
    pdfPages.value = []
    return
  }

  await nextTick()

  const measureEl = pdfMeasureRef.value
  if (!measureEl) {
    pdfPages.value = renderedContent.value ? [renderedContent.value] : []
    return
  }

  const children = Array.from(measureEl.children) as HTMLElement[]
  if (children.length === 0) {
    pdfPages.value = renderedContent.value ? [renderedContent.value] : []
    return
  }

  const maxPageHeight = 980
  const pages: string[] = []
  let currentPageHtml = ''
  let currentHeight = 0

  children.forEach((child) => {
    const childHeight = child.offsetHeight || child.scrollHeight || 0
    const childHtml = child.outerHTML

    if (currentPageHtml && currentHeight + childHeight > maxPageHeight) {
      pages.push(currentPageHtml)
      currentPageHtml = childHtml
      currentHeight = childHeight
      return
    }

    currentPageHtml += childHtml
    currentHeight += childHeight
  })

  if (currentPageHtml) {
    pages.push(currentPageHtml)
  }

  pdfPages.value = pages.length > 0 ? pages : [renderedContent.value]
}

watch(
  () => [props.mode, renderedContent.value],
  () => {
    void recalculatePdfPages()
  },
  { immediate: true }
)

onMounted(() => {
  void recalculatePdfPages()
})

const baseFileName = computed(() => `markdown_render_${new Date().getTime()}`)

function downloadBlob(blob: Blob, fileName: string) {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = fileName
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
}

function createFullHtmlDocument(innerHtml: string, title: string) {
  return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>${title}</title>
  <style>
    body { font-family: "Microsoft YaHei", Arial, sans-serif; color: #1f2937; padding: 32px; line-height: 1.75; }
    h1, h2, h3, h4, h5, h6 { color: #111827; }
    pre { background: #f5f7fa; padding: 16px; border-radius: 8px; overflow: auto; }
    code { background: #f5f7fa; padding: 2px 4px; border-radius: 4px; }
    table { border-collapse: collapse; width: 100%; }
    th, td { border: 1px solid #dcdfe6; padding: 8px 12px; text-align: left; }
    blockquote { border-left: 4px solid #dcdfe6; margin: 1em 0; padding-left: 1em; color: #606266; }
    img { max-width: 100%; }
  </style>
</head>
<body>${innerHtml}</body>
</html>`
}

function htmlToPlainText(html: string) {
  const temp = document.createElement('div')
  temp.innerHTML = html
  return temp.innerText || temp.textContent || ''
}

function escapeRtf(text: string) {
  return text
    .replace(/\\/g, '\\\\')
    .replace(/\{/g, '\\{')
    .replace(/\}/g, '\\}')
    .replace(/\n/g, '\\par\n')
}

async function exportHtml(innerHtml: string, fileName: string) {
  const html = createFullHtmlDocument(innerHtml, fileName)
  downloadBlob(new Blob([html], { type: 'text/html;charset=utf-8' }), fileName)
}

async function exportMarkdown(markdown: string, fileName: string) {
  downloadBlob(new Blob([markdown], { type: 'text/markdown;charset=utf-8' }), fileName)
}

async function exportPdf(element: HTMLElement | null, fileName: string) {
  if (!element) {
    ElMessage.error('当前没有可导出的 PDF 内容')
    return
  }

  await nextTick()
  await loadHtml2Pdf()

  const pdfBlob = await window.html2pdf()
    .set({
      margin: 10,
      filename: fileName,
      image: { type: 'jpeg', quality: 0.98 },
      html2canvas: { scale: 2, useCORS: true },
      jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
    })
    .from(element)
    .outputPdf('blob')

  downloadBlob(pdfBlob, fileName)
}

async function exportDoc(innerHtml: string, fileName: string) {
  const docHtml = createFullHtmlDocument(innerHtml, fileName)
  downloadBlob(new Blob(['\ufeff', docHtml], { type: 'application/msword' }), fileName)
}

async function exportRtf(innerHtml: string, fileName: string) {
  const plainText = htmlToPlainText(innerHtml)
  const rtf = `{\\rtf1\\ansi\\deff0 {\\fonttbl{\\f0 Calibri;}}\n\\fs24 ${escapeRtf(plainText)}}`
  downloadBlob(new Blob([rtf], { type: 'application/rtf' }), fileName)
}

async function handleDownload() {
  if (props.mode === 'markdown') {
    const suffix = activeFlavor.value === 'commonmark' ? '_commonmark' : '_gfm'
    await exportMarkdown(props.content, `${baseFileName.value}${suffix}.md`)
    return
  }
  if (props.mode === 'html') {
    await exportHtml(renderedContent.value, `${baseFileName.value}.html`)
    return
  }
  if (props.mode === 'pdf') {
    await exportPdf(pdfRef.value, `${baseFileName.value}.pdf`)
    return
  }
  if (activeRichtextFormat.value === 'word') {
    await exportDoc(renderedContent.value, `${baseFileName.value}.doc`)
    return
  }
  await exportRtf(renderedContent.value, `${baseFileName.value}.rtf`)
}
</script>

<style scoped>
.markdown-renderer {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: #FFFFFF;
  border-radius: 4px;
  box-shadow: none;
  overflow: hidden;
  min-height: 0;
}

.content-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
}

.content-toolbar-title {
  font-size: 14px;
  font-weight: 600;
  color: #343A40;
  white-space: nowrap;
}

.content-toolbar-meta {
  font-size: 12px;
  color: #86909C;
  white-space: nowrap;
}

.render-actions {
  padding: 0 0 12px;
  background-color: #FFFFFF;
}

.render-actions-left {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 32px;
  flex: 1;
  min-width: 0;
}

.render-actions-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.action-button {
  border-radius: 6px;
  transition: all 0.3s ease;
  height: 32px;
  padding: 0 12px;
}

.action-button.primary {
  background-color: #165dff;
  border-color: #165dff;
  color: #ffffff;
}

.action-button.primary:hover {
  background-color: #0e42d2;
  border-color: #0e42d2;
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(22, 93, 255, 0.3);
}

.render-shell {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.render-content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0;
}

.render-header {
  padding: 14px 16px;
  border-radius: 10px;
  background: linear-gradient(135deg, #f7faff 0%, #eef4ff 100%);
  border: 1px solid #d7e4ff;
}

.render-title {
  font-size: 16px;
  font-weight: 600;
  color: #1f2937;
}

.render-desc {
  margin-top: 4px;
  font-size: 13px;
  line-height: 1.6;
  color: #5b6472;
}

.markdown-content {
  padding: 20px;
  background: white;
  border-radius: 4px;
  min-height: 400px;
}

.rendered-html {
  border: 1px solid #e9ecef;
}

.pdf-stage {
  padding: 20px;
  border: 1px solid #E9ECEF;
  border-radius: 4px;
  background: #F8F9FA;
}

.pdf-preview-pages {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.pdf-page {
  width: min(100%, 840px);
  min-height: 1160px;
  margin: 0 auto;
  background: white;
  border: 1px solid #d9dee8;
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.12);
  padding: 28px 36px 36px;
}

.pdf-content {
  padding: 0;
  min-height: auto;
}

.pdf-measure {
  position: absolute;
  left: -99999px;
  top: 0;
  width: 768px;
  visibility: hidden;
  pointer-events: none;
  z-index: -1;
}

.richtext-stage {
  padding: 12px;
  border: 1px solid #E9ECEF;
  border-radius: 4px;
  background: #F8F9FA;
}

.richtext-document {
  min-height: 100%;
  background: white;
  border: 1px solid #dde3ea;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: none;
}

.richtext-content {
  padding: 32px 40px 48px;
}

.markdown-content :deep(h1) {
  font-size: 2em;
  margin: 0.67em 0;
  font-weight: bold;
}

.markdown-content :deep(h2) {
  font-size: 1.5em;
  margin: 0.83em 0;
  font-weight: bold;
}

.markdown-content :deep(h3) {
  font-size: 1.17em;
  margin: 1em 0;
  font-weight: bold;
}

.markdown-content :deep(p) {
  margin: 1em 0;
  line-height: 1.75;
}

.markdown-content :deep(code) {
  background-color: #f5f7fa;
  padding: 2px 4px;
  border-radius: 3px;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}

.markdown-content :deep(pre) {
  background-color: #f5f7fa;
  padding: 16px;
  border-radius: 8px;
  overflow-x: auto;
}

.markdown-content :deep(pre code) {
  background: none;
  padding: 0;
}

.markdown-content :deep(blockquote) {
  margin: 1em 0;
  padding: 0 1em;
  border-left: 4px solid #dcdfe6;
  color: #606266;
}

.markdown-content :deep(ul), .markdown-content :deep(ol) {
  margin: 1em 0;
  padding-left: 2em;
}

.markdown-content :deep(li) {
  margin: 0.5em 0;
}

.markdown-content :deep(table) {
  border-collapse: collapse;
  width: 100%;
  margin: 1em 0;
}

.markdown-content :deep(th), .markdown-content :deep(td) {
  border: 1px solid #dcdfe6;
  padding: 8px 12px;
  text-align: left;
}

.markdown-content :deep(th) {
  background-color: #f5f7fa;
  font-weight: bold;
}

.markdown-content :deep(img) {
  max-width: 100%;
  height: auto;
}

.empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 400px;
}

.math-inline {
  font-family: 'Cambria Math', 'STIX Two Math', serif;
  font-size: 1.1em;
}

@media (max-width: 768px) {
  .render-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .render-actions-left {
    flex: 1;
    min-width: 0;
    flex-wrap: wrap;
  }

  .render-actions-right {
    justify-content: flex-end;
  }

  .pdf-page {
    padding: 24px 20px 32px;
    min-height: auto;
  }

  .richtext-content {
    padding: 24px 18px 32px;
  }

  .action-button {
    flex-shrink: 0;
  }
}
</style>
