<template>
    <div class="message-bubble-container" :class="{ 'mobile-action-enabled': mobileActionEnabled }">
        <div class="message-content" :class="{
            'seat-side': seatSideMessage,
            'centered-message': centeredMessage,
        }">

            <div v-if="!centeredMessage && !isPC" class="message-avatar" :class="{
                'seat-side': seatSideMessage,
                'clickable': avatarClickable,
            }" aria-hidden="true" @click.stop="handleAvatarClick">
                <img class="message-avatar-image" :src="messageAvatar || defaultAvatar" alt="" />
            </div>

            <div class="message-body" :class="{
                'seat-side': seatSideMessage,
            }">
                <div v-if="!centeredMessage" class="message-body-nickname">
                    <RobotMessageSourceInfo :message="message" />
                    <span>{{ messageShowNickName }}</span>
                    <span v-if="messageShowTime" class="message-body-time">{{ messageShowTime }}</span>
                </div>

                <div class="message-content-with-read-status" :class="{
                    'seat-side': seatSideMessage,
                }">
                    <div class="message-body-content" :class="{
                        'seat-side': seatSideMessage,
                        'no-padding': noPadding,
                        'no-background': noBackground,
                        'file-message': needFileBorder,
                        'administrator-side': administratorMessage,
                        'quoted-media': isQuotedMediaMessage,
                    }" :id="`${message.ID}content`" @contextmenu="handleContextMenu" @touchstart="handleTouchStart"
                        @touchmove="handleTouchMove" @touchend="handleTouchEnd" @touchcancel="handleTouchCancel">
                        <MessageQuote :message="message" :message-list-dom="messageListDom ?? null" :status="status" />
                        <slot />
                    </div>

                    <button v-if="!centeredMessage && messageFailStatus" type="button"
                        class="message-body-fail-status" :aria-label="t('重发消息')"
                        @click.stop="emit('resend-message', message)">!</button>

                    <div v-if="!centeredMessage && messageNeedShowReadStatus && !messageFailStatus"
                        class="message-body-read-status">
                        <img v-if="Number(message?.readReceiptInfo?.readCount) > 0" class="message-read-status-icon"
                            :src="readedIcon" :alt="t('已读')" />
                        <img v-else class="message-read-status-icon" :src="unreadIcon" :alt="t('未读')" />
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import dayjs from 'dayjs'

import { CUSTOM_MESSAGE_SRC, JSONToObject, MESSAGE_TYPES, type IMessageModel, type SessionState } from '@tencentcloud/ai-desk-core'

import { isInterveneMergeMessage, isSeatSideMessage } from '../../../../utils'
import MessageQuote from './MessageQuote.vue'
import RobotMessageSourceInfo from './RobotMessageSourceInfo'
import { isMobile, isPC } from '../../../../utils/env'
import defaultAvatar from '../../../../assets/icons/defaultAvatar.svg'
import readedIcon from './readed.svg'
import unreadIcon from './unread.svg'

interface CustomerServicePayload {
    src?: string
}

interface Props {
    message: IMessageModel
    messageStatus?: string
    sessionState?: SessionState
    isGroupSession?: boolean
    peerSource?: string
    messageListDom?: HTMLDivElement | null
    staffShowReadReceipt?: number
    onContextMenu?: (event: MouseEvent) => void
    onAvatarClick?: (message: IMessageModel) => void
}

const props = defineProps<Props>()

const emit = defineEmits<{
    (e: 'context-menu', event: MouseEvent): void
    (e: 'resend-message', message: IMessageModel): void
}>()

const { t } = useI18n()

const longPressTimer = ref<number | null>(null)
const longPressTriggered = ref(false)
const ignoreNextNativeContextMenu = ref(false)

const centeredLayoutMessageSrcWhiteList: string[] = [
    CUSTOM_MESSAGE_SRC.MENU,
    CUSTOM_MESSAGE_SRC.MENU_SELECTED,
    CUSTOM_MESSAGE_SRC.NO_SEAT_ONLINE,
    CUSTOM_MESSAGE_SRC.END,
    CUSTOM_MESSAGE_SRC.TIMEOUT,
    CUSTOM_MESSAGE_SRC.USER_TIMEOUT_REMIND,
    CUSTOM_MESSAGE_SRC.SESSION_TAKEOVER,
]

const safeToString = (input: unknown): string => {
    if (typeof input === 'string') {
        return input
    }

    if (typeof input === 'number' || typeof input === 'boolean') {
        return String(input)
    }

    return ''
}

const getCustomerServicePayload = (message: IMessageModel): CustomerServicePayload | null => {
    const payloadData = message?.payload?.data
    const parsedPayload = JSONToObject(payloadData)

    if (parsedPayload) {
        return parsedPayload
    }

    if (message?.payload && typeof message.payload === 'object') {
        return message.payload as CustomerServicePayload
    }

    return null
}

const getMessageAvatar = (message: IMessageModel) => {
    return message.avatar
}

const shouldUseCenteredLayout = (message: IMessageModel) => {
    if (message.type === MESSAGE_TYPES.MSG_CUSTOM) {
        const customerServicePayload = getCustomerServicePayload(message)
        return centeredLayoutMessageSrcWhiteList.includes(customerServicePayload?.src ?? '')
    }
    if (message.type === MESSAGE_TYPES.MSG_MERGER) {
        return isInterveneMergeMessage(message)
    }
    return false
}

const centeredMessage = computed(() => shouldUseCenteredLayout(props.message))

const FILE_IMAGE_EXT_REGEXP = /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i
const FILE_VIDEO_EXT_REGEXP = /\.(mp4|webm|ogg|mov|m4v|avi|wmv|mkv)(\?.*)?$/i

const matchFileType = (value: string | undefined, regexp: RegExp) => {
    if (!value) {
        return false
    }

    return regexp.test(value)
}

const getFileMessageUrl = (message: IMessageModel) => {
    const payload = message?.payload

    if (!payload || typeof payload !== 'object') {
        return ''
    }

    return safeToString(Reflect.get(payload as object, 'fileUrl') || Reflect.get(payload as object, 'url'))
}

const getFileMessageName = (message: IMessageModel) => {
    const payload = message?.payload

    if (!payload || typeof payload !== 'object') {
        return ''
    }

    return safeToString(Reflect.get(payload as object, 'fileName') || Reflect.get(payload as object, 'name'))
}

const isImageOrVideoFileMessage = (message: IMessageModel) => {
    if (message.type !== MESSAGE_TYPES.MSG_FILE) {
        return false
    }

    const fileUrl = getFileMessageUrl(message)
    const fileName = getFileMessageName(message)

    return matchFileType(fileUrl, FILE_IMAGE_EXT_REGEXP) ||
        matchFileType(fileName, FILE_IMAGE_EXT_REGEXP) ||
        matchFileType(fileUrl, FILE_VIDEO_EXT_REGEXP) ||
        matchFileType(fileName, FILE_VIDEO_EXT_REGEXP)
}

const actionEnabled = computed(() => {
    return (isPC || isMobile) &&
        !centeredMessage.value &&
        !props.isGroupSession &&
        props.sessionState !== 'finished'
})

const mobileActionEnabled = computed(() => isMobile && actionEnabled.value)

const isNoPadding = (message: IMessageModel) => {
    try {
        if (
            message.type === MESSAGE_TYPES.MSG_IMAGE ||
            message.type === MESSAGE_TYPES.MSG_VIDEO ||
            message.type === MESSAGE_TYPES.MSG_FILE
        ) {
            return true
        }

        if (message.type === MESSAGE_TYPES.MSG_MERGER) {
            return isInterveneMergeMessage(message)
        }

        if (message.type === MESSAGE_TYPES.MSG_CUSTOM) {
            const customerServicePayload = getCustomerServicePayload(message)
            const whiteList: string[] = [
                CUSTOM_MESSAGE_SRC.MENU,
                CUSTOM_MESSAGE_SRC.MENU_SELECTED,
                CUSTOM_MESSAGE_SRC.BRANCH,
                CUSTOM_MESSAGE_SRC.NO_SEAT_ONLINE,
                CUSTOM_MESSAGE_SRC.END,
                CUSTOM_MESSAGE_SRC.TIMEOUT,
                CUSTOM_MESSAGE_SRC.WELCOME_CARD,
                CUSTOM_MESSAGE_SRC.NEW_BRANCH,
                CUSTOM_MESSAGE_SRC.USER_TIMEOUT_REMIND,
                CUSTOM_MESSAGE_SRC.SESSION_TAKEOVER,
            ]

            return whiteList.includes(customerServicePayload?.src ?? '')
        }

        return false
    } catch {
        return false
    }
}

const isNoBackground = (message: IMessageModel) => {
    try {
        if (
            message.type === MESSAGE_TYPES.MSG_IMAGE ||
            message.type === MESSAGE_TYPES.MSG_VIDEO ||
            message.type === MESSAGE_TYPES.MSG_FILE
        ) {
            return true
        }

        if (message.type === MESSAGE_TYPES.MSG_MERGER) {
            return isInterveneMergeMessage(message)
        }

        if (message.type === MESSAGE_TYPES.MSG_CUSTOM) {
            const customerServicePayload = getCustomerServicePayload(message)
            const whiteList: string[] = [
                CUSTOM_MESSAGE_SRC.MENU,
                CUSTOM_MESSAGE_SRC.MENU_SELECTED,
                CUSTOM_MESSAGE_SRC.BRANCH,
                CUSTOM_MESSAGE_SRC.NO_SEAT_ONLINE,
                CUSTOM_MESSAGE_SRC.END,
                CUSTOM_MESSAGE_SRC.TIMEOUT,
                CUSTOM_MESSAGE_SRC.WELCOME_CARD,
                CUSTOM_MESSAGE_SRC.NEW_BRANCH,
                CUSTOM_MESSAGE_SRC.USER_TIMEOUT_REMIND,
                CUSTOM_MESSAGE_SRC.SESSION_TAKEOVER,
            ]

            return whiteList.includes(customerServicePayload?.src ?? '')
        }

        return false
    } catch {
        return false
    }
}

const clearTextSelection = () => {
    window.getSelection?.()?.removeAllRanges()
}

const emitContextMenu = (event: MouseEvent) => {
    props.onContextMenu?.(event)
    emit('context-menu', event)
}

const handleContextMenu = (event: MouseEvent) => {
    if (!actionEnabled.value) {
        return
    }

    event.preventDefault()
    event.stopPropagation()

    if (isMobile && ignoreNextNativeContextMenu.value) {
        ignoreNextNativeContextMenu.value = false
        return
    }

    emitContextMenu(event)
}

const clearLongPressTimer = () => {
    if (longPressTimer.value !== null) {
        window.clearTimeout(longPressTimer.value)
        longPressTimer.value = null
    }
}

const handleTouchStart = (event: TouchEvent) => {
    if (!isMobile || !actionEnabled.value) {
        return
    }

    longPressTriggered.value = false
    clearLongPressTimer()

    const touch = event.touches[0]

    if (!touch) {
        return
    }

    const { clientX, clientY } = touch
    longPressTimer.value = window.setTimeout(() => {
        longPressTriggered.value = true
        ignoreNextNativeContextMenu.value = true
        clearTextSelection()
        const longPressEvent = new MouseEvent('contextmenu', {
            bubbles: true,
            cancelable: true,
            clientX,
            clientY,
        })
        emitContextMenu(longPressEvent)
        clearLongPressTimer()
    }, 500)
}

const handleTouchMove = () => {
    longPressTriggered.value = false
    clearLongPressTimer()
}

const handleTouchEnd = () => {
    clearLongPressTimer()
}

const handleTouchCancel = () => {
    longPressTriggered.value = false
    ignoreNextNativeContextMenu.value = false
    clearLongPressTimer()
}

const avatarClickable = computed(() => !centeredMessage.value && !seatSideMessage.value)

const handleAvatarClick = () => {
    if (!avatarClickable.value) {
        return
    }

    props.onAvatarClick?.(props.message)
}

onBeforeUnmount(() => {
    clearLongPressTimer()
})

const seatSideMessage = computed(() => isSeatSideMessage(props.message))
const administratorMessage = computed(() => props.message?.from === 'administrator')
const hasQuoteMessage = computed(() => {
    try {
        if (!props.message.cloudCustomData) {
            return false
        }
        return Boolean(JSON.parse(props.message.cloudCustomData)?.messageReply)
    } catch {
        return false
    }
})
const isMediaMessage = computed(() => [
    MESSAGE_TYPES.MSG_IMAGE,
    MESSAGE_TYPES.MSG_VIDEO,
    MESSAGE_TYPES.MSG_FILE,
].includes(props.message.type as MESSAGE_TYPES))
const isQuotedMediaMessage = computed(() => hasQuoteMessage.value && isMediaMessage.value)
const noPadding = computed(() => isNoPadding(props.message) && !isQuotedMediaMessage.value)
const noBackground = computed(() => isNoBackground(props.message) && !isQuotedMediaMessage.value)
const isFileMessage = computed(() => props.message.type === MESSAGE_TYPES.MSG_FILE)
const needFileBorder = computed(() => isFileMessage.value && !isImageOrVideoFileMessage(props.message))
const status = computed(() => props.sessionState === 'finished' ? '400' : '200')
const messageFailStatus = computed(() => props.messageStatus === 'fail')
const messageAvatar = computed(() => getMessageAvatar(props.message))

const messageNeedShowReadStatus = computed(() => {
    return !props.isGroupSession &&
        (props.message.flow === 'out' || props.message.flow === 'out-others') &&
        isSeatSideMessage(props.message) &&
        Boolean(props.message.needReadReceipt) &&
        props.staffShowReadReceipt === 1 &&
        ['4', '10'].includes(props.peerSource ?? '0')
})

const messageShowNickName = computed(() => {
    if (props.message?.from === 'administrator') {
        return t('机器人')
    }
    return props.message?.nick || props.message?.from || ''
})

const messageShowTime = computed(() => {
    const timestamp = Number(props.message?.time)
    if (!Number.isFinite(timestamp) || timestamp <= 0) {
        return ''
    }

    const normalizedTimestamp = timestamp < 1e12 ? timestamp * 1000 : timestamp
    return dayjs(normalizedTimestamp).format('YYYY-MM-DD HH:mm:ss')
})
</script>

<style scoped>
.message-bubble-container {
    width: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    padding: 0 10px 25px;
    user-select: text;
    -webkit-touch-callout: none;
    -webkit-user-select: text;
    -khtml-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
}

.message-bubble-container.mobile-action-enabled,
.message-bubble-container.mobile-action-enabled * {
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}

.message-content {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
}

.message-content.seat-side {
    flex-direction: row-reverse;
    justify-content: flex-start;
}

.message-content.centered-message {
    justify-content: center;
}

.message-avatar {
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.message-avatar.clickable {
    cursor: pointer;
}

.message-avatar-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
    display: block;
}

.message-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin: 0 8px;
    max-width: calc(100% - 54px);
    flex: 0 1 auto;
}

.message-body.seat-side {
    align-items: flex-end;
}

.message-body-nickname {
    margin-bottom: 4px;
    font-size: 12px;
    color: rgba(153, 162, 178, 1);
    line-height: 18px;
    display: flex;
    align-items: center;
    gap: 6px;
}

.message-body-time {
    color: rgba(153, 162, 178, 0.8);
    white-space: nowrap;
}

.message-content-with-read-status {
    display: flex;
    flex-direction: row;
}

.message-content-with-read-status.seat-side {
    flex-direction: row-reverse;
}

.message-body-content {
    display: flex;
    flex-direction: column;
    min-width: 0;
    box-sizing: border-box;
    padding: 12px;
    font-size: 14px;
    color: #000;
    letter-spacing: 0;
    word-wrap: break-word;
    word-break: break-all;
    position: relative;
    background: #FFFFFF;
    border-radius: 0 10px 10px;
}

.message-body-content.seat-side {
    background: #EEF0F5;
    border-radius: 10px 0 10px 10px;
}

.message-body-content.quoted-media {
    gap: 8px;
    overflow: hidden;
}

.message-body-content.administrator-side {
    background: #f0ecff;
}

.message-body-content.no-padding {
    padding: 0;
}

.message-body-content.no-background {
    background: transparent;
}

.message-body-content.file-message {
    border: 1px solid #e8e8e8;
}

.message-body-read-status {
    flex-shrink: 0;
    width: fit-content;
    align-self: flex-end;
    padding: 0 10px;
}

.message-read-status {
    font-size: 10px;
}

.message-read-status.is-read {
    color: #b6b8ba;
}

.message-read-status.is-unread {
    color: #679ce1;
}

.message-read-status-icon {
    width: 14px;
    height: 14px;
    display: block;
}

.message-body-fail-status {
    width: 15px;
    height: 15px;
    padding: 0;
    border: 0;
    border-radius: 15px;
    align-self: center;
    color: #fff;
    display: flex;
    justify-content: center;
    align-items: center;
    background: red;
    margin-right: 10px;
    cursor: pointer;
}

.message-action-dot-container {
    align-self: center;
    margin: 0 0 5px 10px;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    user-select: none;
    transition: opacity 0.15s ease;
}

.message-action-dot-container.seat-side {
    margin: 0 10px 5px 0;
}

.message-action-dot-container.show-dot {
    opacity: 1;
}
</style>