<template>
    <div class="message-list-container" :class="{ 'is-pc': !isMobile }" @click="handleContainerClick">
        <div id="messageScrollList" ref="messageListRef" class="message-scroll-list"
            :style="{ paddingBottom: messageListPaddingBottom }" @scroll="handleScroll">
            <div v-if="shouldShowNoMoreMessageTip" class="message-system-tip">
                {{ t('没有更多消息') }}
            </div>
            <div ref="messageElementListRef" v-for="(message, index) in messageList" :id="message.ID"
                :key="message.ID || `message-${index}`" class="message-item" data-elementtype="message"
                :data-data="serializeMessage(message)">
                <MessageTimestamp :currTime="message.time" :prevTime="messageList[index - 1]?.time || 0" />
                <div class="message-element">
                    <RevokedMessage v-if="message.isRevoked" :message="message" :reEditMessage="handleReEditMessage"
                        :session-state="sessionState" />
                    <template v-else-if="message.type === MESSAGE_TYPES.MSG_GRP_TIP">
                        <div class="message-system-tip">
                            {{ formatGroupTipsMessage(message) }}
                        </div>
                    </template>
                    <MessageBubble v-else :message="message" :message-status="message.status"
                        :session-state="sessionState"
                        :is-group-session="currentSession?.sessionData.imAgentChatType === 1"
                        :peer-source="currentSession?.sessionData.peerSource" :message-list-dom="messageListRef"
                        :staff-show-read-receipt="staffShowReadReceipt" :on-avatar-click="props.onAvatarClick"
                        :on-context-menu="(event) => openContextMenu(message, event)"
                        @resend-message="openResendConfirmDialog">
                        <TextMessage v-if="message.type === MESSAGE_TYPES.MSG_TEXT" :data="message.payload"
                            :message="message" :translate-info="currentSessionTranslateTextInfo[message.ID]" />

                        <ImageMessage v-else-if="message.type === MESSAGE_TYPES.MSG_IMAGE"
                            :image-content="getImageContent(message)" :meida-message-onload="handleMediaLoaded"
                            :preview-image="() => openImagePreview(message)" />

                        <VideoMessage v-else-if="message.type === MESSAGE_TYPES.MSG_VIDEO"
                            :meida-message-onload="handleMediaLoaded" :video-content="getVideoContent(message)" />

                        <AudioMessage v-else-if="message.type === MESSAGE_TYPES.MSG_AUDIO"
                            :audio-content="getAudioContent(message)" :is-seat-side="isSeatSideMessage(message)" />

                        <FileMessage v-else-if="message.type === MESSAGE_TYPES.MSG_FILE"
                            :file-content="getFileContent(message)" :is-seat-side="isSeatSideMessage(message)"
                            :preview-image="() => openImagePreview(message)"
                            :meida-message-onload="handleMediaLoaded" />

                        <MergeMessage v-else-if="message.type === MESSAGE_TYPES.MSG_MERGER"
                            :merge-content="getMergeContent(message)" :current-user-id="currnetUserId"
                            :is-intervene-message="isInterveneMergeMessage(message)" />

                        <StreamMessage v-else-if="message.type === MESSAGE_TYPES.MSG_STREAM"
                            :markdown="getStreamContent(message)" />

                        <CustomMessage v-else-if="message.type === MESSAGE_TYPES.MSG_CUSTOM" :message="message"
                            @preview-image="(previewImageId) => openImagePreview(message, previewImageId)" />

                        <div v-else class="unsupported-message-card">
                            {{ t('暂不支持的消息类型：{type}', { type: message.type || 'unknown' }) }}
                        </div>
                    </MessageBubble>
                </div>
            </div>
        </div>

        <button v-if="footerAction" class="session-footer-action"
            :class="{ 'session-footer-action--pending': isFooterActionPending }" :disabled="isFooterActionDisabled"
            type="button" @click="handleFooterActionClick">
            {{ footerAction.label }}
        </button>

        <ScrollButton v-if="showScrollButton" :scroll-to-message-list-bottom="scrollToBottom" />

        <MessageTools v-if="contextMenu && contextMenuMessage" :context-menu="contextMenu" :message="contextMenuMessage"
            :is-group-session="currentSession?.sessionData.imAgentChatType === 1"
            :translate-info="currentSessionTranslateTextInfo[contextMenuMessage.ID]" @close="closeContextMenu"
            :current-user-id="currnetUserId" />

        <MobileImagePreview :visible="mobileImagePreviewVisible" :images="mobileImagePreviewList"
            :active-index="mobileImagePreviewIndex" @close="closeMobileImagePreview"
            @update:active-index="handleMobileImagePreviewIndexChange" />

        <FloatingImageWindow v-for="win in windows" :key="win.id" :state="win" @close="closeWindow"
            @bring-to-front="bringToFront" @update="updateWindow" />

        <t-dialog class="resend-confirm-dialog" v-model:visible="resendConfirmVisible"
            :header="isMobile ? undefined : t('提示')" :footer="!isMobile" :confirm-btn="null" :cancel-btn="null"
            :close-on-overlay-click="!resendPending" @cancel="closeResendConfirmDialog"
            @close="closeResendConfirmDialog">
            <template #middle>
                <div v-if="isMobile" class="resend-dialog-content">
                    <div class="resend-dialog-title">{{ t('提示') }}</div>
                    <div class="resend-dialog-text">{{ t('是否重发该消息？') }}</div>
                    <div class="resend-dialog-actions">
                        <button type="button" class="resend-dialog-button resend-dialog-button--cancel"
                            :disabled="resendPending" @click="closeResendConfirmDialog">
                            {{ t('取消') }}
                        </button>
                        <button type="button" class="resend-dialog-button resend-dialog-button--confirm"
                            :disabled="resendPending" @click="handleResendConfirm">
                            {{ t('确定') }}
                        </button>
                    </div>
                </div>
            </template>
            <template #body>
                <div v-if="!isMobile" class="resend-dialog-text">{{ t('是否重发该消息？') }}</div>
            </template>
            <template #footer>
                <div v-if="!isMobile" class="resend-dialog-actions resend-dialog-actions--pc">
                    <button type="button" class="resend-dialog-button resend-dialog-button--cancel"
                        :disabled="resendPending" @click="closeResendConfirmDialog">
                        {{ t('取消') }}
                    </button>
                    <button type="button" class="resend-dialog-button resend-dialog-button--confirm"
                        :disabled="resendPending" @click="handleResendConfirm">
                        {{ t('确定') }}
                    </button>
                </div>
            </template>
        </t-dialog>
    </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import debounce from 'lodash/debounce'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'

import AIDeskCoreInstance, {
    MESSAGE_TYPES,
    SESSION_TYPE,
    type IMessageModel,
    type ISessionModel,
    type SessionState,
} from '@tencentcloud/ai-desk-core'

import {
    formatGroupTipsMessage,
    getPreviewSourceMessageId,
    getPreviewableImageItems,
    isInterveneMergeMessage,
    isSeatSideMessage,
    type PreviewImageItem,
} from '../../utils'
import toast from '../../utils/toast'
import AudioMessage from './components/AudioMessage/index.vue'
import CustomMessage from './components/CustomMeaage/index.vue'
import FileMessage from './components/FileMessage.vue'
import FloatingImageWindow from './components/FloatingImageWindow.vue'
import ImageMessage from './components/ImageMessage.vue'
import MergeMessage from './components/MergeMessage/index.vue'
import MessageBubble from './components/MessageBubble/index.vue'
import MessageTimestamp from './components/MessageTimestamp.vue'
import MobileImagePreview from './components/MobileImagePreview.vue'
import MessageTools from './components/MessageTools/index.vue'
import RevokedMessage from './components/RevokedMessage.vue'
import ScrollButton from './components/ScrollButton.vue'
import TextMessage from './components/TextMessage/index.vue'
import VideoMessage from './components/VideoMessage.vue'
import StreamMessage from './components/StreamMessage.vue'
import { useFloatingImageManager } from './useFloatingImageManager'
import { StoreName } from '@tencentcloud/ai-desk-core'
import { isMobile } from '../../utils/env'
import { parseUnknownError } from '../../utils/parseUnknownError.ts'

interface ScrollConfig {
    scrollToMessage?: IMessageModel;
    scrollToBottom?: boolean;
    scrollToOffset?: {
        top?: number;
        bottom?: number;
    };
}

interface ContextMenuState {
    mouseX: number
    mouseY: number
}

interface MessageListProps {
    reEditMessage?: (message: IMessageModel) => void
    footerActionDisabled?: boolean
    onAvatarClick?: (message: IMessageModel) => void
    onSwitchToMyTab?: () => void
}

interface MobilePreviewImageItem {
    id: string
    url: string
}

interface FooterActionConfig {
    label: string
    action: 'restart-session' | 'takeover-robot-session' | 'takeover-queue-session' | 'start-seat-private-chat'
}

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

type CurrentSessionTranslateTextInfo = Record<string, TranslateTextInfoItem>

type TranslateTextInfo = Record<string, CurrentSessionTranslateTextInfo>

const props = defineProps<MessageListProps>()
const router = useRouter()
const { t } = useI18n()

const HANDLE_SCROLL_DEBOUNCE_MS = 80
const HISTORY_LOAD_TRIGGER_THRESHOLD = 40
const MOBILE_CONTEXT_MENU_CLICK_GUARD_MS = 250

let observer: any = null;
const sentReceiptMessageIDSet = new Set<string>();
const messageListRef = ref<HTMLDivElement | null>(null)
const messageElementListRef = ref<HTMLElement[] | null>(null)
const isOverOneScreen = ref(false)
const isCompleted = ref(false);
const beforeHistoryGetScrollHeight = ref<number>(0);
const isLoadingHistory = ref(false)
const messageList = ref<IMessageModel[]>([]);
const currentLastMessage = ref<IMessageModel>();
const currentSession = ref<ISessionModel | null>(null)
const sessionState = ref<SessionState>("")
const sessionType = ref<SESSION_TYPE | "">("")
const isFooterActionPending = ref(false)
const shouldScrollToBottomOnSessionEnter = ref(false)
const { windows, openImage, closeWindow, bringToFront, updateWindow } = useFloatingImageManager()
const contextMenu = ref<ContextMenuState | null>(null)
const contextMenuMessage = ref<IMessageModel | null>(null)
const currentSessionTranslateTextInfo = ref<CurrentSessionTranslateTextInfo>({})
const lastContextMenuOpenedAt = ref(0)
const mobileImagePreviewVisible = ref(false)
const mobileImagePreviewIndex = ref(0)
const staffShowReadReceipt = ref(0)
const currnetUserId = ref<string>('');
const resendConfirmVisible = ref(false)
const resendPending = ref(false)
const resendMessage = ref<IMessageModel | null>(null)

const openResendConfirmDialog = (message: IMessageModel) => {
    resendMessage.value = message
    resendConfirmVisible.value = true
}

const closeResendConfirmDialog = () => {
    if (resendPending.value) {
        return
    }
    resendConfirmVisible.value = false
    resendMessage.value = null
}

const handleResendConfirm = async () => {
    if (!resendMessage.value || resendPending.value) {
        return
    }

    try {
        resendPending.value = true
        await AIDeskCoreInstance.chatService.resendMessage(resendMessage.value)
        resendConfirmVisible.value = false
        resendMessage.value = null
    } catch (error) {
        console.error(error)
        toast.error(parseUnknownError(error))
    } finally {
        resendPending.value = false
    }
}

const sessionImageList = computed<PreviewImageItem[]>(() => {
    return messageList.value.flatMap((item) => getPreviewableImageItems(item as IMessageModel))
})

const mobileImagePreviewList = computed<MobilePreviewImageItem[]>(() => {
    return sessionImageList.value.map((item) => ({
        id: item.messageId,
        url: item.src,
    }))
})

const footerAction = computed<FooterActionConfig | null>(() => {
    switch (sessionType.value) {
        case SESSION_TYPE.IM:
            return sessionState.value === 'finished'
                ? {
                    label: t('重新会话'),
                    action: 'restart-session',
                }
                : null
        case SESSION_TYPE.ROBOT:
            return {
                label: t('介入会话'),
                action: 'takeover-robot-session',
            }
        case SESSION_TYPE.IM_QUEUE:
            return {
                label: t('介入会话'),
                action: 'takeover-queue-session',
            }
        case SESSION_TYPE.IM_OTHER_INPROGRESS:
            return {
                label: t('私聊座席'),
                action: 'start-seat-private-chat',
            }
        default:
            return null
    }
})

const isStatusControlledFooterAction = computed(() => {
    return footerAction.value?.action === 'takeover-robot-session'
        || footerAction.value?.action === 'takeover-queue-session'
        || footerAction.value?.action === 'start-seat-private-chat'
})

const isFooterActionDisabled = computed(() => {
    return isFooterActionPending.value || (
        isStatusControlledFooterAction.value
        && Boolean(props.footerActionDisabled)
    )
})

const messageListPaddingBottom = computed(() => {
    return footerAction.value ? '92px' : ''
})

const shouldShowNoMoreMessageTip = computed(() => {
    return isCompleted.value
        && messageList.value.length > 0
        && !(
            sessionType.value === SESSION_TYPE.IM
            && sessionState.value === 'finished'
        )
})

const shouldAutoScrollToBottomForCurrentSession = computed(() => {
    if (!currentSession.value) {
        return false
    }

    return !(
        currentSession.value.sessionData.type === SESSION_TYPE.IM
        && sessionState.value === 'finished'
    )
})

onMounted(() => {
    AIDeskCoreInstance.coreStore.watch(StoreName.SESSION, {
        currentSession: onCurrentSessionUpdated,
        translateTextInfo: onTranslateTextInfoUpdated,
    });
    AIDeskCoreInstance.coreStore.watch(StoreName.CHAT, {
        messageList: onMessageListUpdated,
        isCompleted: onChatCompletedUpdated,
    });
    AIDeskCoreInstance.coreStore.watch(StoreName.APP_SETTING, {
        staffShowReadReceipt: onStaffShowReadReceiptUpdated,
    });
    AIDeskCoreInstance.coreStore.watch(StoreName.USERINFO, {
        userId: onCurrentUserIdUpdated,
    });
});

onUnmounted(() => {
    AIDeskCoreInstance.coreStore.unwatch(StoreName.SESSION, {
        currentSession: onCurrentSessionUpdated,
        translateTextInfo: onTranslateTextInfoUpdated,
    });
    AIDeskCoreInstance.coreStore.unwatch(StoreName.CHAT, {
        messageList: onMessageListUpdated,
        isCompleted: onChatCompletedUpdated,
    });
    AIDeskCoreInstance.coreStore.unwatch(StoreName.APP_SETTING, {
        staffShowReadReceipt: onStaffShowReadReceiptUpdated,
    });
    AIDeskCoreInstance.coreStore.unwatch(StoreName.USERINFO, {
        userId: onCurrentUserIdUpdated,
    });

    handleScroll.cancel()
    observer?.disconnect();
    observer = null;
});

const onTranslateTextInfoUpdated = (translateTextInfo: TranslateTextInfo) => {
    const sessionId = currentSession.value?.sessionId
    currentSessionTranslateTextInfo.value = sessionId
        ? (translateTextInfo[sessionId] ?? {})
        : {}
}

const onCurrentUserIdUpdated = (userId: string) => {
    currnetUserId.value = userId;
};

const onStaffShowReadReceiptUpdated = (data: number) => {
    staffShowReadReceipt.value = data
}

const onChatCompletedUpdated = (flag: boolean) => {
    isCompleted.value = flag;
}

const onCurrentSessionUpdated = (session: {
    sessionId: string;
    sessionType: SESSION_TYPE | '';
}) => {
    const sessionInfo = AIDeskCoreInstance.coreStore.getSessionInfo(session.sessionId, session.sessionType);
    currentSession.value = sessionInfo ?? null
    sessionType.value = session.sessionType;
    sessionState.value = sessionInfo && sessionInfo.sessionData.type === SESSION_TYPE.IM
        ? sessionInfo.sessionData.sessionState
        : ''
    shouldScrollToBottomOnSessionEnter.value = !!sessionInfo && !(
        sessionInfo.sessionData.type === SESSION_TYPE.IM
        && sessionInfo.sessionData.sessionState === 'finished'
    )
    beforeHistoryGetScrollHeight.value = 0
    isLoadingHistory.value = false
    isOverOneScreen.value = false
}

const onMessageListUpdated = async (list: IMessageModel[]) => {
    observer?.disconnect();
    const oldMessageList = messageList.value;
    const oldLastMessage = currentLastMessage.value;
    const revokedPreviewMessageIds = new Set(
        list
            .filter((message) => {
                if (!message.isRevoked || !message.ID) {
                    return false;
                }

                const previousMessage = oldMessageList.find((item) => item.ID === message.ID);
                return previousMessage && !previousMessage.isRevoked;
            })
            .map((message) => message.ID),
    );

    messageList.value = list

    if (revokedPreviewMessageIds.size > 0) {
        const previewableImageItemsIncludingRevoked = list.flatMap((item) => {
            return getPreviewableImageItems(item as IMessageModel, { includeRevoked: true })
        })
        const revokedPreviewItemIds = previewableImageItemsIncludingRevoked
            .filter((item) => revokedPreviewMessageIds.has(getPreviewSourceMessageId(item.messageId)))
            .map((item) => item.messageId)
        const revokedPreviewItemIdSet = new Set(revokedPreviewItemIds)
        let hasClosedPreview = false

        windows.value
            .filter((win) => revokedPreviewItemIdSet.has(win.activeImageId))
            .forEach((win) => {
                closeWindow(win.id)
                hasClosedPreview = true
            })

        if (mobileImagePreviewVisible.value) {
            const currentMobileImage = mobileImagePreviewList.value[mobileImagePreviewIndex.value]
            if (currentMobileImage && revokedPreviewItemIdSet.has(currentMobileImage.id)) {
                closeMobileImagePreview()
                hasClosedPreview = true
            }
        }

        if (hasClosedPreview) {
            toast.warn(t('当前预览的消息已被撤回'))
        }
    }

    const newLastMessage = messageList.value?.[messageList.value?.length - 1];
    if (beforeHistoryGetScrollHeight.value) {
        await scrollToPosition({
            scrollToOffset: { bottom: beforeHistoryGetScrollHeight.value },
        });
        beforeHistoryGetScrollHeight.value = 0;
    } else if (shouldScrollToBottomOnSessionEnter.value && newLastMessage?.ID) {
        await scrollToPosition({ scrollToBottom: true });
        shouldScrollToBottomOnSessionEnter.value = false;
    } else if (isOverOneScreen.value && newLastMessage && !isSeatSideMessage(newLastMessage)) {
        return;
    } else if (
        newLastMessage?.ID
        && JSON.stringify(oldLastMessage) !== JSON.stringify(newLastMessage)
    ) {
        await scrollToPosition({ scrollToBottom: true });
    }
    currentLastMessage.value = Object.assign({}, newLastMessage);
    nextTick(() => bindIntersectionObserver());
}

async function scrollToPosition(config: ScrollConfig = {}): Promise<void> {
    return new Promise((resolve, reject) => {
        requestAnimationFrame(() => {
            if (!messageListRef.value) {
                reject();
            }
            const container = messageListRef.value;
            if (config.scrollToBottom) {
                container!.scrollTop = container!.scrollHeight;
            } else if (config.scrollToMessage) {
                const targetMessageDom = messageElementListRef.value?.find(
                    (dom: HTMLElement) => dom?.id === `tui-${config.scrollToMessage?.ID}`,
                );
                if (targetMessageDom?.scrollIntoView) {
                    targetMessageDom.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                }
            } else if (config.scrollToOffset) {
                if (typeof config.scrollToOffset?.top === 'number') {
                    container!.scrollTop = config.scrollToOffset.top;
                } else if (typeof config.scrollToOffset?.bottom === 'number') {
                    container!.scrollTop = container!.scrollHeight - config.scrollToOffset.bottom;
                }
            }
            resolve();
        });
    });
}

async function bindIntersectionObserver() {
    if (
        !messageList.value
        || !messageListRef.value
        || messageList.value.length === 0
    ) {
        return;
    }

    const mappingFromIDToMessage: Record<string, {
        msgDom: HTMLElement;
        msgModel: IMessageModel | undefined;
    }> = {};

    observer?.disconnect();
    observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
            const { isIntersecting, target } = entry;
            if (isIntersecting) {
                const mappingItem = mappingFromIDToMessage[target.id];
                if (!mappingItem) {
                    return;
                }

                const { msgDom, msgModel } = mappingItem;

                if (
                    msgModel
                    && !msgModel.readReceiptInfo?.isPeerRead
                    && !sentReceiptMessageIDSet.has(msgModel.ID)
                ) {
                    AIDeskCoreInstance.chatService.sendMessageReadReceipt([msgModel]);
                    sentReceiptMessageIDSet.add(msgModel.ID);
                    observer?.unobserve(msgDom);
                }
            }
        });
    }, {
        root: messageListRef.value,
        threshold: 0.7,
    });

    const arrayOfMessageLi = messageListRef.value?.querySelectorAll("[data-elementtype='message']",);

    if (arrayOfMessageLi) {
        for (let i = 0; i < arrayOfMessageLi?.length; ++i) {
            const messageElement = arrayOfMessageLi[i] as HTMLElement;
            const matchingMessage = messageList.value.find((message: IMessageModel) => {
                return messageElement.id === message.ID;
            });
            if (
                matchingMessage
                && matchingMessage.needReadReceipt
                && !isSeatSideMessage(matchingMessage)
            ) {
                mappingFromIDToMessage[messageElement.id] = {
                    msgDom: messageElement,
                    msgModel: matchingMessage,
                };
                observer?.observe(messageElement);
            }
        }
    }
}

const getHistoryMessageList = async (shouldKeepScrollPosition = true) => {
    if (isLoadingHistory.value) {
        return
    }

    try {
        isLoadingHistory.value = true

        if (shouldKeepScrollPosition && messageListRef.value) {
            beforeHistoryGetScrollHeight.value = messageListRef.value.scrollHeight;
        }

        await AIDeskCoreInstance.chatService.getCurrertSessionMessageList()
    } finally {
        isLoadingHistory.value = false
    }
};


const showScrollButton = computed(() => isOverOneScreen.value && sessionState.value === 'inProgress')

const serializeMessage = (message: IMessageModel) => {
    try {
        return JSON.stringify(message)
    } catch {
        return ''
    }
}

const getImageContent = (message: IMessageModel) => {
    const imageInfo = message.payload?.imageInfoArray?.[0] ?? {}
    return {
        url: imageInfo.url ?? '',
    }
}

const getVideoContent = (message: IMessageModel) => {
    return {
        url: message.payload.videoUrl,
        snapshotUrl: message.payload.snapshotUrl || message.payload.thumbUrl,
        snapshotWidth: message.payload.snapshotWidth || message.payload.thumbWidth,
        snapshotHeight: message.payload.snapshotHeight || message.payload.thumbHeight,
    }
}

const getAudioContent = (message: IMessageModel) => {
    return {
        url: message.payload?.url ?? '',
        second: Number(message.payload?.second) || 1,
    }
}

const getFileContent = (message: IMessageModel) => {
    return {
        name: message.payload?.fileName ?? message.payload?.name ?? '',
        url: message.payload?.fileUrl ?? message.payload?.url ?? '',
        size: message.payload?.fileSize ?? message.payload?.size ?? '',
        uploadProgress: message.payload?.uploadProgress,
    }
}

const getMergeContent = (message: IMessageModel) => {
    return {
        messageList: message.payload?.messageList ?? [],
        title: message.payload?.title ?? '',
        abstractList: message.payload?.abstractList ?? [],
        cloudCustomData: message?.cloudCustomData ?? '',
    };
}

const getStreamContent = (message: IMessageModel) => {
    return message.payload?.markdown ?? ''
}

const closeContextMenu = () => {
    contextMenu.value = null
    contextMenuMessage.value = null
}

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

const handleContainerClick = () => {
    if (
        isMobile
        && contextMenu.value
        && Date.now() - lastContextMenuOpenedAt.value < MOBILE_CONTEXT_MENU_CLICK_GUARD_MS
    ) {
        return
    }

    closeContextMenu()
}

const openContextMenu = (message: IMessageModel, event: MouseEvent) => {
    if (sessionState.value !== 'inProgress') {
        return
    }

    if (isMobile) {
        clearNativeSelection()
        lastContextMenuOpenedAt.value = Date.now()
    }

    contextMenuMessage.value = message
    contextMenu.value = {
        mouseX: event.clientX + 2,
        mouseY: event.clientY - 6,
    }
}

const handleReEditMessage = (message: IMessageModel) => {
    props.reEditMessage?.(message)
}

const navigateToSessionList = async () => {
    AIDeskCoreInstance.sessionService.switchSession({ sessionId: '', sessionType: '' })

    if (isMobile) {
        await router.push('/mobileDesk/sessionList')
    } else {
        props.onSwitchToMyTab?.()
    }
}

const handleFooterActionClick = async () => {
    const sessionInfo = currentSession.value
    const action = footerAction.value?.action

    if (!sessionInfo || !action || isFooterActionDisabled.value) {
        return
    }

    try {
        isFooterActionPending.value = true
        switch (action) {
            case 'restart-session': {
                if (sessionInfo.sessionData.type !== SESSION_TYPE.IM) {
                    return
                }
                await AIDeskCoreInstance.sessionService.followUpSession(
                    sessionInfo.sessionData.userId,
                    sessionInfo.sessionData.channelAgentID,
                    sessionInfo.sessionData.peerSource,
                    1
                )
                break
            }
            case 'takeover-robot-session': {
                if (sessionInfo.sessionData.type !== SESSION_TYPE.ROBOT) {
                    return
                }
                await AIDeskCoreInstance.sessionService.takeoverRobotSession(
                    sessionInfo.sessionData.userId,
                    sessionInfo.sessionData.channelAgentID,
                    sessionInfo.sessionData.peerSource,
                )
                break
            }
            case 'takeover-queue-session': {
                if (sessionInfo.sessionData.type !== SESSION_TYPE.IM_QUEUE) {
                    return
                }
                await AIDeskCoreInstance.sessionService.takeOverIMQueueSession(
                    sessionInfo.sessionData.userId,
                    sessionInfo.sessionData.channelAgentID,
                    sessionInfo.sessionData.peerSource,
                )
                break
            }
            case 'start-seat-private-chat': {
                if (sessionInfo.sessionData.type !== SESSION_TYPE.IM_OTHER_INPROGRESS) {
                    return
                }
                await AIDeskCoreInstance.sessionService.interveneOtherSeatInprogressSession(
                    sessionInfo.sessionId,
                )
                break
            }
            default:
                return
        }

        await navigateToSessionList()
    } catch (error) {
        console.error(error)
        toast.error(parseUnknownError(error))
    } finally {
        isFooterActionPending.value = false
    }
}

const closeMobileImagePreview = () => {
    mobileImagePreviewVisible.value = false
}

const handleMobileImagePreviewIndexChange = (index: number) => {
    mobileImagePreviewIndex.value = index
}

const openImagePreview = (message: IMessageModel, previewImageId?: string) => {
    const targetImageId = previewImageId || message.ID
    const currentImage = sessionImageList.value.find((item) => item.messageId === targetImageId)
    if (!currentImage) {
        return
    }

    if (!isMobile) {
        openImage({
            imageId: currentImage.messageId,
            src: currentImage.src,
            imageList: sessionImageList.value.map((item) => ({
                id: item.messageId,
                src: item.src,
            })),
        })
        return
    }

    const targetIndex = mobileImagePreviewList.value.findIndex((item) => item.id === currentImage.messageId)
    mobileImagePreviewIndex.value = targetIndex >= 0 ? targetIndex : 0
    mobileImagePreviewVisible.value = true
}

const updateScrollState = () => {
    const container = messageListRef.value
    if (!container || sessionState.value !== 'inProgress') {
        return
    }

    const { height } = container.getBoundingClientRect()
    const { scrollHeight, scrollTop } = container
    const farFromBottom = scrollHeight - scrollTop > 2 * height
    isOverOneScreen.value = farFromBottom
}

const handleScroll = debounce(async () => {
    updateScrollState()

    const container = messageListRef.value
    if (!container || !messageList.value.length || isCompleted.value || isLoadingHistory.value) {
        return
    }

    if (sessionType.value === SESSION_TYPE.IM && sessionState.value === 'finished') {
        const isScrolledToBottom = container.scrollTop + container.clientHeight + HISTORY_LOAD_TRIGGER_THRESHOLD >= container.scrollHeight

        if (isScrolledToBottom) {
            await getHistoryMessageList(false)
        }
    } else {
        const isScrolledToTop = container.scrollTop <= HISTORY_LOAD_TRIGGER_THRESHOLD

        if (isScrolledToTop) {
            await getHistoryMessageList()
        }
    }
}, HANDLE_SCROLL_DEBOUNCE_MS)

const scrollToBottom = async () => {
    await nextTick()

    const container = messageListRef.value
    if (!container) {
        return
    }

    container.scrollTop = container.scrollHeight
    updateScrollState()
}

const handleMediaLoaded = async () => {
    if (!isOverOneScreen.value && shouldAutoScrollToBottomForCurrentSession.value) {
        await scrollToBottom()
    }
}

</script>

<style scoped>
.message-list-container {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
    background: #f5f7fa;
}

.message-list-container.is-pc {
    background: #ffffff;
}

/* PC 端：进来的消息气泡（非座席侧）背景 */
.message-list-container.is-pc :deep(.message-body-content:not(.seat-side):not(.no-background)) {
    background: rgba(243, 244, 247, 1);
}

.message-scroll-list {
    flex: 1;
    min-height: 0;
    overflow: hidden auto;
    padding: 16px 12px 20px;
}

.message-scroll-list::-webkit-scrollbar {
    width: 5px;
}

.message-scroll-list::-webkit-scrollbar-track {
    background: transparent;
}

.message-scroll-list::-webkit-scrollbar-thumb {
    background: #c7cad1;
    border-radius: 999px;
    visibility: hidden;
}

.message-scroll-list:hover::-webkit-scrollbar-thumb {
    visibility: visible;
}

.message-item {
    display: flex;
    flex-direction: column;
}

.message-element {
    display: flex;
    flex-direction: column;
}

.message-system-tip {
    display: flex;
    justify-content: center;
    width: 100%;
    margin-bottom: 10px;
    font-size: 12px;
    color: #999;
    text-align: center;
}

.session-footer-action {
    position: absolute;
    left: 50%;
    bottom: 24px;
    transform: translateX(-50%);
    width: min(400px, calc(100% - 64px));
    height: 44px;
    border: none;
    border-radius: 22px;
    background: #000;
    color: #fff;
    font-size: 15px;
    font-weight: 500;
    line-height: 44px;
    text-align: center;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.16);
    z-index: 2;
}

.session-footer-action--pending,
.session-footer-action:disabled {
    background: #C0C3CC;
    cursor: not-allowed;
    box-shadow: none;
}

.merge-message-wrapper {
    padding: 0 18px 25px;
}

.unsupported-message-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    font-size: 14px;
    line-height: 1.6;
    color: #4f566b;
    white-space: pre-wrap;
}

.resend-dialog-content {
    padding: 20px 16px 16px;
}

.resend-dialog-title {
    color: #1d2129;
    font-size: 18px;
    font-weight: 600;
    line-height: 26px;
    text-align: center;
}

.resend-dialog-text {
    margin-top: 12px;
    color: #1d2129;
    font-size: 16px;
    line-height: 24px;
    text-align: left;
}

.resend-dialog-actions {
    display: flex;
    gap: 12px;
    margin-top: 24px;
}

.resend-dialog-actions--pc {
    justify-content: flex-end;
    margin-top: 0;
}

.resend-dialog-button {
    min-width: 88px;
    height: 40px;
    padding: 0 20px;
    border: none;
    border-radius: 999px;
    font-size: 15px;
    cursor: pointer;
}

.resend-dialog-content .resend-dialog-button {
    flex: 1;
}

.resend-dialog-button--cancel {
    background: #f0f2f7;
    color: #000;
}

.resend-dialog-button--confirm {
    background: #000;
    color: #fff;
}

.resend-dialog-button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
</style>