<script setup lang="ts">
import { computed, nextTick, ref, useTemplateRef, watchEffect } from 'vue'
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import 'katex/dist/katex.min.css'
import { throttle, debounce } from 'lodash-es'
import DOMPurify from 'dompurify'
import hljs from 'highlight.js'
import 'highlight.js/styles/panda-syntax-light.css'
import 'github-markdown-css'
import markedKatex, { MarkedKatexOptions } from 'marked-katex-extension'
import { copyToClip } from '@/utils/copy'
import { useI18n } from 'vue-i18n'

interface Props {
  rawTextContent: string
  fontSize?: number | string
  color?: string
}

const props = withDefaults(defineProps<Props>(), {
  rawTextContent: '',
  fontSize: '14px',
  color: '#333',
})

const katexOptions: MarkedKatexOptions = {
  throwOnError: false,
  displayMode: true,
  nonStandard: true,
  output: 'html',
}

const markdownRenderContainerRef = useTemplateRef<HTMLDivElement>('markdownRenderContainerRef')
const { t } = useI18n()

const marked = new Marked()
  .use({ gfm: true, async: true, breaks: true })
  .use(
    markedHighlight({
      emptyLangClass: 'hljs',
      langPrefix: 'hljs language-',
      highlight(code, lang, _info) {
        const language = hljs.getLanguage(lang) ? lang : 'plaintext'

        // return hljs.highlight(code, { language }).value
        const str = hljs.highlight(code, { language }).value

        return `<div class="code-render-container"><div class="code-operation-bar-container"><span>${language}</span><span class="code-copy-btn iconfont icon-copy"></span></div><pre class="code-render-inner"><code>${str}</code></pre></div>`
      },
    }),
  )
  .use(markedKatex(katexOptions))
  .use({
    hooks: {
      preprocess(markdown: string) {
        /**
         * 1. 将 \(...\) 和 \\(...\\) 转为 $...$ 以支持行内公式
         * 2. 将 \[...\] 和 \\[...\\]  转为 $$...$$ 以支持块级公式
         */

        const katexTextReplace = markdown
          .replace(/\\\\\(|\\\\\)|\\\(|\\\)/g, '$')
          .replace(/\\\\\[|\\\\\]|\\\[|\\\]/g, '$$$$')

        return katexTextReplace
      },
      postprocess(html: string) {
        return DOMPurify.sanitize(html)
      },
    },
  })

let btnEventController = new AbortController()

const renderTextContent = ref('')

const articleContainerStyle = computed(() => {
  return {
    fontSize: props.fontSize,
    color: props.color,
  }
})

const textContentParser = throttle(
  (text: string) => {
    ;(marked.parse(text) as Promise<string>).then((res) => {
      renderTextContent.value = res
    })
  },
  500,
  { leading: true, trailing: true },
)

const codeCopyBtnEventBind = debounce(
  () => {
    if (markdownRenderContainerRef.value) {
      const codeCopyBtnElList = markdownRenderContainerRef.value.getElementsByClassName('code-copy-btn')

      btnEventController.abort()
      btnEventController = new AbortController()

      /* 遍历 */
      ;[...codeCopyBtnElList].forEach((elItem) => {
        elItem.addEventListener(
          'click',
          () => {
            const code = elItem.parentElement?.nextElementSibling?.firstChild?.textContent

            if (code) {
              copyToClip(code).then(() => {
                window.$message.success(t('common_module.copy_success_message'))
              })
            }
          },
          { signal: btnEventController.signal },
        )
      })
    }
  },
  1000,
  { leading: false, trailing: true },
)

watchEffect(() => {
  textContentParser(props.rawTextContent)

  nextTick(() => {
    setTimeout(() => {
      codeCopyBtnEventBind()
    }, 60)
  })
})

function getRenderTextContent() {
  return renderTextContent.value
}

defineExpose({
  getRenderTextContent,
})
</script>

<template>
  <div ref="markdownRenderContainerRef" class="markdown-render-container">
    <article class="markdown-body markdown-render-inner" :style="articleContainerStyle" v-html="renderTextContent" />
  </div>
</template>

<style lang="scss" scoped>
@include custom-scrollbar(6px);

.markdown-render-container {
  .markdown-render-inner {
    overflow-x: auto;
    font-family: 'Microsoft YaHei UI';
    word-break: break-all;
    background-color: unset;

    & > :deep(pre) {
      padding: 0;
    }
  }

  :deep(.code-render-container) {
    font-family: 'Microsoft YaHei UI';
    font-size: v-bind('props.fontSize');

    .code-operation-bar-container {
      display: flex;
      justify-content: space-between;
      padding: 10px 16px;
      user-select: none;
      background-color: #f1f1f1;

      .code-copy-btn {
        cursor: pointer;
        transition: color ease-in-out 0.3s;

        &:hover {
          color: #777ef9;
        }
      }
    }

    .code-render-inner {
      padding: 10px 16px 0;
      margin: 0;
      overflow-x: auto;
    }
  }
}

:deep(pre) {
  &::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  }

  &::-webkit-scrollbar-corner {
    background: transparent;
  }

  &::-webkit-scrollbar-thumb {
    background: #bfbfbf;
    border-radius: 5px;
  }

  &::-webkit-scrollbar-track {
    background: transparent;
    border-radius: 2px;
  }
}
</style>
