<template>
    <div class="text-message-root">
        <div class="text-message-inline">
            <template v-for="(item, index) in renderDom" :key="`item-${index}`">
                <span v-if="item.name === 'text' && item.text">
                    <template v-for="(part, partIndex) in splitTextParts(item.text)"
                        :key="`text-${index}-${partIndex}`">
                        <a v-if="isLink(part)" class="text-message-link" :href="part" target="_blank"
                            rel="noopener noreferrer">
                            {{ part }}
                        </a>
                        <span v-else class="text-message-text">
                            {{ part }}
                        </span>
                    </template>
                </span>

                <span v-else-if="item.name === 'img'">
                    <img class="text-message-emoji" draggable="false" :src="item.src" alt="" />
                </span>
            </template>
        </div>

        <div v-if="shouldShowTranslateResult" class="text-message-translate" :class="{
            'is-loading': translateStatus === 'loading',
            'is-error': translateStatus === 'error',
        }">
            <div class="text-message-translate-content">
                <template v-if="translateStatus === 'success'">
                    <template v-for="(item, index) in translateRenderDom" :key="`translate-${index}`">
                        <span v-if="item.name === 'text' && item.text">
                            <template v-for="(part, partIndex) in splitTextParts(item.text)"
                                :key="`translate-text-${index}-${partIndex}`">
                                <a v-if="isLink(part)" class="text-message-link" :href="part" target="_blank"
                                    rel="noopener noreferrer">
                                    {{ part }}
                                </a>
                                <span v-else class="text-message-text">
                                    {{ part }}
                                </span>
                            </template>
                        </span>

                        <span v-else-if="item.name === 'img'">
                            <img class="text-message-emoji" draggable="false" :src="item.src" alt="" />
                        </span>
                    </template>
                </template>

                <template v-else>
                    {{ translateDisplayText }}
                </template>
            </div>

            <div v-if="showLanguageTrigger" class="text-message-translate-language-wrapper">
                <button ref="triggerRef" type="button" class="text-message-translate-language-trigger"
                    :class="{ 'is-open': showLanguagePanel }" :aria-label="t('切换语言')" @click.stop="toggleLanguagePanel">
                    <span class="text-message-translate-language-trigger-dot"></span>
                    <span class="text-message-translate-language-trigger-dot"></span>
                    <span class="text-message-translate-language-trigger-dot"></span>
                </button>

                <div v-if="showLanguagePanel" ref="panelRef" class="text-message-translate-language-panel"
                    :style="panelStyle" @click.stop>
                    <div class="text-message-translate-language-panel__title">
                        {{ t('将后续收到的内容翻译为') }}
                    </div>
                    <div class="text-message-translate-language-panel__list">
                        <button v-for="item in languageOptions" :key="item.key" type="button" :class="[
                            'text-message-translate-language-item',
                            { 'is-active': item.key === currentTargetLanguage },
                        ]" @click="handleSelectLanguage(item.key)">
                            <span :class="[
                                'text-message-translate-language-item__key',
                                { 'is-active': item.key === currentTargetLanguage },
                            ]">{{ item.key }}</span>
                            <span class="text-message-translate-language-item__label">{{ item.label }}</span>
                            <span v-if="item.key === currentTargetLanguage"
                                class="text-message-translate-language-item__check">✓</span>
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch, type CSSProperties } from 'vue'
import { useI18n } from 'vue-i18n'

import AIDeskCoreInstance, {
    StoreName,
    type IMessageModel,
} from '@tencentcloud/ai-desk-core'

import { decodeText } from '../../../../utils/decodeText'
import { isPC } from '../../../../utils/env'
import { parseUnknownError } from '../../../../utils/parseUnknownError'
import toast from '../../../../utils/toast'

interface TextMessageData {
    text: string
}

interface RenderDomItem {
    name: string
    src?: string
    text?: string
}

interface TranslateTextInfoItem {
    translateResult: string
    translateStatus: 'loading' | 'success' | 'error'
}

interface LanguageOption {
    key: string
    label: string
}

interface TextElementProps {
    data: TextMessageData
    translateInfo?: TranslateTextInfoItem
    message?: IMessageModel
}

const props = defineProps<TextElementProps>()

const { t } = useI18n()

const linkSplitPattern = /(https?:\/\/[^\s\u4e00-\u9fa5，。！？；：]+)/gi
const linkCheckPattern = /^https?:\/\/[^\s\u4e00-\u9fa5，。！？；：]+$/i

const languageOptions: LanguageOption[] = [
    { key: 'zh', label: t('中文') },
    { key: 'zh-TW', label: t('繁体中文') },
    { key: 'en', label: t('英文') },
    { key: 'ja', label: t('日语') },
    { key: 'ko', label: t('韩语') },
    { key: 'fr', label: t('法语') },
    { key: 'es', label: t('西班牙语') },
    { key: 'it', label: t('意大利语') },
    { key: 'de', label: t('德语') },
    { key: 'tr', label: t('土耳其语') },
    { key: 'ru', label: t('俄语') },
    { key: 'pt', label: t('葡萄牙语') },
    { key: 'vi', label: t('越南语') },
    { key: 'id', label: t('印尼语') },
    { key: 'th', label: t('泰语') },
    { key: 'ms', label: t('马来语') },
    { key: 'ar', label: t('阿拉伯语') },
]

const renderDom = computed(() => decodeText(props.data) as RenderDomItem[])
const translateStatus = computed(() => props.translateInfo?.translateStatus)
const translateRenderDom = computed(() => {
    return decodeText({
        text: props.translateInfo?.translateResult ?? '',
    }) as RenderDomItem[]
})
const shouldShowTranslateResult = computed(() => {
    if (translateStatus.value === 'loading' || translateStatus.value === 'error') {
        return true
    }

    return translateStatus.value === 'success' && Boolean(props.translateInfo?.translateResult)
})
const translateDisplayText = computed(() => {
    if (translateStatus.value === 'loading') {
        return t('翻译中...')
    }

    if (translateStatus.value === 'error') {
        return t('翻译失败')
    }

    return props.translateInfo?.translateResult ?? ''
})

const showLanguageTrigger = computed(() => {
    return isPC
        && !!props.message
        && translateStatus.value === 'success'
        && Boolean(props.translateInfo?.translateResult)
})

const currentTargetLanguage = ref('zh')
const showLanguagePanel = ref(false)
const triggerRef = ref<HTMLButtonElement | null>(null)
const panelRef = ref<HTMLDivElement | null>(null)
const panelStyle = ref<CSSProperties>({})

const onMessageListTranslateTargetLanguageUpdated = (newLanguage: string) => {
    currentTargetLanguage.value = newLanguage || 'zh'
}

onMounted(() => {
    AIDeskCoreInstance.coreStore.watch(StoreName.USERINFO, {
        messsageListTranslateTargetLanguage: onMessageListTranslateTargetLanguageUpdated,
    })
})

onUnmounted(() => {
    AIDeskCoreInstance.coreStore.unwatch(StoreName.USERINFO, {
        messsageListTranslateTargetLanguage: onMessageListTranslateTargetLanguageUpdated,
    })
})

const splitTextParts = (text: string) => text.split(linkSplitPattern)

const isLink = (text: string) => linkCheckPattern.test(text)

const PANEL_WIDTH = 240
const PANEL_MAX_HEIGHT = 360
const VIEWPORT_GAP = 12
const TRIGGER_OFFSET = 6

const updatePanelPosition = () => {
    const triggerEl = triggerRef.value

    if (!triggerEl) {
        panelStyle.value = {}
        return
    }

    const rect = triggerEl.getBoundingClientRect()
    const viewportWidth = window.innerWidth
    const viewportHeight = window.innerHeight
    const nextStyle: CSSProperties = { position: 'fixed' }

    const panelEl = panelRef.value
    const measuredHeight = panelEl?.offsetHeight ?? 0
    const desiredHeight = measuredHeight > 0 ? measuredHeight : PANEL_WIDTH
    const availableHeight = viewportHeight - VIEWPORT_GAP * 2
    const cappedHeight = Math.min(PANEL_MAX_HEIGHT, availableHeight, Math.max(desiredHeight, 120))

    const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_GAP - TRIGGER_OFFSET
    const spaceAbove = rect.top - VIEWPORT_GAP - TRIGGER_OFFSET
    const openUpwards = spaceBelow < cappedHeight && spaceAbove > spaceBelow

    const finalMaxHeight = Math.max(
        120,
        Math.min(cappedHeight, openUpwards ? spaceAbove : spaceBelow, availableHeight),
    )

    if (openUpwards) {
        const bottom = Math.max(VIEWPORT_GAP, viewportHeight - rect.top + TRIGGER_OFFSET)
        nextStyle.bottom = `${bottom}px`
    } else {
        const maxTop = viewportHeight - VIEWPORT_GAP - finalMaxHeight
        const desiredTop = rect.bottom + TRIGGER_OFFSET
        const top = Math.max(VIEWPORT_GAP, Math.min(desiredTop, maxTop))
        nextStyle.top = `${top}px`
    }

    const preferredLeft = rect.left
    const clampedLeft = Math.max(
        VIEWPORT_GAP,
        Math.min(preferredLeft, viewportWidth - PANEL_WIDTH - VIEWPORT_GAP),
    )
    nextStyle.left = `${clampedLeft}px`
    nextStyle.maxHeight = `${finalMaxHeight}px`

    panelStyle.value = nextStyle
}

const onDocumentClick = (event: MouseEvent) => {
    const target = event.target as Node

    if (triggerRef.value?.contains(target)) {
        return
    }

    if (panelRef.value?.contains(target)) {
        return
    }

    closeLanguagePanel()
}

const onWindowResize = () => {
    if (!showLanguagePanel.value) return
    updatePanelPosition()
}

const bindOutsideEvents = () => {
    document.addEventListener('mousedown', onDocumentClick, true)
    window.addEventListener('resize', onWindowResize)
    window.addEventListener('scroll', onWindowResize, true)
}

const unbindOutsideEvents = () => {
    document.removeEventListener('mousedown', onDocumentClick, true)
    window.removeEventListener('resize', onWindowResize)
    window.removeEventListener('scroll', onWindowResize, true)
}

const closeLanguagePanel = () => {
    if (!showLanguagePanel.value) return
    showLanguagePanel.value = false
    unbindOutsideEvents()
}

const openLanguagePanel = async () => {
    showLanguagePanel.value = true
    await nextTick()
    updatePanelPosition()
    // 第二次基于真实渲染高度再校准一次，避免超出屏幕
    await nextTick()
    updatePanelPosition()
    bindOutsideEvents()
}

const toggleLanguagePanel = () => {
    if (showLanguagePanel.value) {
        closeLanguagePanel()
    } else {
        openLanguagePanel()
    }
}

const handleSelectLanguage = async (targetLanguage: string) => {
    closeLanguagePanel()

    if (!props.message) {
        return
    }

    try {
        AIDeskCoreInstance.userInfoService.updateMesssageListTranslateTargetLanguage(targetLanguage)
        await AIDeskCoreInstance.chatService.translateTextMessage({
            message: props.message,
            targetLanguage,
        })
    } catch (error) {
        toast.error(t('切换语言失败：{reason}', { reason: parseUnknownError(error) }))
    }
}

watch(showLanguageTrigger, (visible) => {
    if (!visible) {
        closeLanguagePanel()
    }
})

onBeforeUnmount(() => {
    unbindOutsideEvents()
})
</script>

<style scoped>
.text-message-inline {
    display: inline;
}

.text-message-text {
    display: inline;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 14px;
    text-size-adjust: none;
}

.text-message-emoji {
    width: 20px;
    height: 20px;
    vertical-align: bottom;
    font-size: 0;
}

.text-message-link {
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 14px;
    text-size-adjust: none;
}

.text-message-translate {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #F9FAFC;
    font-size: 13px;
    line-height: 20px;
    color: #676A70;
    display: flex;
    flex-direction: row;
    align-items: flex-end;
    gap: 8px;
}

.text-message-translate-content {
    flex: 1;
    min-width: 0;
    white-space: pre-wrap;
    word-break: break-word;
}

.text-message-translate .text-message-text,
.text-message-translate .text-message-link {
    color: #676A70;
}

.text-message-translate.is-loading,
.text-message-translate.is-error {
    color: #8b95a7;
}

.text-message-translate-language-wrapper {
    position: relative;
    flex-shrink: 0;
    align-self: flex-end;
    line-height: 0;
}

.text-message-translate-language-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
    width: 26px;
    height: 22px;
    padding: 0 4px;
    border: none;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: background-color 0.15s ease, opacity 0.15s ease;
}

.text-message-root:hover .text-message-translate-language-trigger,
.text-message-translate-language-trigger.is-open {
    opacity: 1;
    pointer-events: auto;
}

.text-message-translate-language-trigger:hover,
.text-message-translate-language-trigger.is-open {
    background: #F5F6F8;
}

.text-message-translate-language-trigger-dot {
    display: inline-block;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: #676A70;
}

.text-message-translate-language-panel {
    position: fixed;
    z-index: 1300;
    width: 240px;
    max-height: 360px;
    padding: 8px 0;
    background: #ffffff;
    border-radius: 12px;
    box-shadow: 0 6px 24px rgba(15, 23, 42, 0.16);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.text-message-translate-language-panel__title {
    padding: 4px 16px 8px;
    color: #B7BAC0;
    font-size: 13px;
    line-height: 20px;
    text-align: left;
}

.text-message-translate-language-panel__list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0 8px;
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
}

.text-message-translate-language-panel__list::-webkit-scrollbar {
    width: 4px;
}

.text-message-translate-language-panel__list::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.15);
    border-radius: 4px;
}

.text-message-translate-language-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    height: 36px;
    padding: 0 8px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: #1d2129;
    font-size: 13px;
    line-height: 20px;
    cursor: pointer;
    text-align: left;
}

.text-message-translate-language-item:hover {
    background: #F5F6F8;
}

.text-message-translate-language-item.is-active {
    background: #F2F6FF;
}

.text-message-translate-language-item__key {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    height: 22px;
    padding: 0 8px;
    border: 1px solid #E4E6EB;
    border-radius: 4px;
    background: #ffffff;
    color: #676A70;
    font-size: 12px;
    line-height: 18px;
}

.text-message-translate-language-item__key.is-active {
    border-color: #3370FF;
    color: #3370FF;
    background: #EAF1FF;
}

.text-message-translate-language-item__label {
    flex: 1;
    color: #1d2129;
}

.text-message-translate-language-item.is-active .text-message-translate-language-item__label {
    color: #1d2129;
    font-weight: 500;
}

.text-message-translate-language-item__check {
    color: #3370FF;
    font-size: 14px;
    font-weight: 600;
}
</style>