<template>
    <div class="message-stream-container">
        <div v-if="renderResult.error" class="message-stream-error">
            {{ renderResult.error }}
        </div>
        <div v-else ref="richTextRef" class="rich-text" v-html="renderResult.html" @click="handleRichTextClick"></div>
    </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import DOMPurify from 'dompurify'
import { marked } from 'marked'

interface StreamMessageProps {
    markdown: string
}

const props = defineProps<StreamMessageProps>()

const emit = defineEmits<{
    (event: 'preview-image', payload: { index: number; src: string }): void
}>()

const richTextRef = ref<HTMLDivElement | null>(null)

const DEFAULT_ERROR_MESSAGE = 'Markdown Error. Please check markdown content.'

const markedRenderer = new marked.Renderer();
const imageRenderer = markedRenderer.image;

markedRenderer.image = (href, title, text) => {
    const html = imageRenderer.call(markedRenderer, href, title, text);
    return `<div class="richText-image-container">${html}</div>`;
};
markedRenderer.link = function (href, _title, text) {
    const targetAttribute = 'target="_blank" rel="noreferrer noopener"';
    const link = `<a href="${href}"${targetAttribute}>${text}</a>`;
    return link;
};

const renderResult = computed(() => {
    try {
        const markdown = props.markdown

        if (typeof markdown !== 'string') {
            return {
                html: '',
                error: DEFAULT_ERROR_MESSAGE,
            }
        }

        const htmlString = marked.parse(markdown, {
            async: false,
            renderer: markedRenderer,
        }) as string

        const cleanContent = DOMPurify.sanitize(htmlString, {
            ADD_ATTR: ['target'],
        })

        return {
            html: cleanContent,
            error: '',
        }
    } catch (error) {
        console.error(error)

        return {
            html: '',
            error: DEFAULT_ERROR_MESSAGE,
        }
    }
})

const handleRichTextClick = (event: MouseEvent) => {
    const container = richTextRef.value
    const target = event.target

    if (!(target instanceof HTMLElement) || !container) {
        return
    }

    const imageElement = target.closest('img')
    if (!(imageElement instanceof HTMLImageElement)) {
        return
    }

    const imageList = Array.from(container.querySelectorAll('img'))
    const imageIndex = imageList.findIndex((item) => item === imageElement)

    if (imageIndex < 0 || !imageElement.src) {
        return
    }

    emit('preview-image', {
        index: imageIndex,
        src: imageElement.src,
    })
}
</script>

<style scoped>
.message-stream-container {
    word-break: break-all;
    font-size: 14px;
}

.message-stream-error {
    word-break: break-word;
}

.rich-text :deep(img) {
    display: block;
    max-width: min(calc(100vw - 180px), 400px);
    max-height: min(calc(100vw - 180px), 400px);
    height: auto;
    margin-top: 6px;
}

.rich-text :deep(video) {
    display: block;
    max-width: min(calc(100vw - 180px), 400px);
    max-height: min(calc(100vw - 180px), 400px);
    height: auto;
    margin-top: 6px;
}

.rich-text :deep(p) {
    margin: 0;
}

.rich-text :deep(pre) {
    white-space: pre-wrap;
    word-break: break-all;
    overflow-x: auto;
    max-width: 100%;
    padding: 12px;
    background-color: #f5f5f5;
    border-radius: 4px;
    margin: 8px 0;
}

.rich-text :deep(code) {
    font-family: monospace;
    font-size: 14px;
}
</style>