<template>
    <div v-if="contextMenu" class="pc-message-context-menu" :style="menuStyle" @click.stop @contextmenu.prevent>
        <button v-for="item in visibleActionItems" :key="item.key" class="pc-message-context-menu-item" type="button"
            :disabled="item.disabled" @click="handleActionClick(item)">
            <img class="pc-message-context-menu-item__icon" :src="item.icon" :alt="item.label">
            <span class="pc-message-context-menu-item__label">{{ item.label }}</span>
        </button>
    </div>
</template>

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

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

import copyIcon from '../../assets/copy.svg'
import revokeIcon from '../../assets/delete.svg'
import quoteIcon from '../../assets/quote.svg'
import translateIcon from '../../assets/translate.svg'
import { parseUnknownError } from '../../../../utils/parseUnknownError'
import toast from '../../../../utils/toast'
import { formateMessageAbstract, getReplyMessageOriginType } from '../../../../utils'

interface MessageToolsContextMenu {
    mouseX: number
    mouseY: number
}

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

interface MenuAction {
    key: string
    label: string
    icon: string
    visible: boolean
    disabled?: boolean
    click: () => Promise<void> | void
}

interface Props {
    contextMenu: MessageToolsContextMenu
    message: IMessageModel
    isGroupSession: boolean
    translateInfo?: TranslateTextInfoItem
    currentUserId?: string
}

const props = defineProps<Props>()

const emit = defineEmits<{
    (e: 'close'): void
    (e: 'translate', message: IMessageModel): void
}>()

const { t } = useI18n()

const MENU_VIEWPORT_GAP = 12
// PC 端菜单预估尺寸（用于智能定位，不影响真实渲染）
const PC_MENU_ITEM_HEIGHT = 36
const PC_MENU_VERTICAL_PADDING = 8
const PC_MENU_ESTIMATED_WIDTH = 140

const messageListTranslateTargetLanguage = ref('zh')
const userMsgTranslationEnable = ref(0)
const seatMsgTranslationEnable = ref(0)

onMounted(() => {
    AIDeskCoreInstance.coreStore.watch(StoreName.USERINFO, {
        messsageListTranslateTargetLanguage: onMessageListTranslateTargetLanguageUpdated,
    });
    AIDeskCoreInstance.coreStore.watch(StoreName.APP_SETTING, {
        userMsgTranslationEnable: onUserMsgTranslationEnableUpdated,
        seatMsgTranslationEnable: onSeatMsgTranslationEnableUpdated,
    });
});

onUnmounted(() => {
    AIDeskCoreInstance.coreStore.unwatch(StoreName.USERINFO, {
        messsageListTranslateTargetLanguage: onMessageListTranslateTargetLanguageUpdated,
    });
    AIDeskCoreInstance.coreStore.unwatch(StoreName.APP_SETTING, {
        userMsgTranslationEnable: onUserMsgTranslationEnableUpdated,
        seatMsgTranslationEnable: onSeatMsgTranslationEnableUpdated,
    });
});

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

const onUserMsgTranslationEnableUpdated = (value: number) => {
    userMsgTranslationEnable.value = value ?? 0
}

const onSeatMsgTranslationEnableUpdated = (value: number) => {
    seatMsgTranslationEnable.value = value ?? 0
}

const getEstimatedMenuHeight = () => {
    const itemCount = Math.max(visibleActionItems.value.length, 1)
    return PC_MENU_VERTICAL_PADDING * 2 + itemCount * PC_MENU_ITEM_HEIGHT
}

const menuStyle = computed<CSSProperties>(() => {
    const viewportWidth = window.innerWidth
    const viewportHeight = window.innerHeight
    const { mouseX, mouseY } = props.contextMenu
    const estimatedMenuHeight = Math.min(getEstimatedMenuHeight(), viewportHeight - MENU_VIEWPORT_GAP * 2)

    return {
        ...(mouseX + PC_MENU_ESTIMATED_WIDTH > viewportWidth - MENU_VIEWPORT_GAP
            ? { right: `${Math.max(MENU_VIEWPORT_GAP, viewportWidth - mouseX)}px` }
            : { left: `${Math.max(MENU_VIEWPORT_GAP, mouseX)}px` }),
        ...(mouseY + estimatedMenuHeight > viewportHeight - MENU_VIEWPORT_GAP
            ? { bottom: `${Math.max(MENU_VIEWPORT_GAP, viewportHeight - mouseY)}px` }
            : { top: `${Math.max(MENU_VIEWPORT_GAP, mouseY)}px` }),
    }
})

const getMessageOpenUrl: (message: IMessageModel) => string = (message: IMessageModel) => {
    if (message.type === MESSAGE_TYPES.MSG_IMAGE) {
        return message.payload?.imageInfoArray?.[0]?.url ?? ''
    }
    return ''
}

const canCopyMessage = computed(() => {
    const message = props.message
    if (message.type === MESSAGE_TYPES.MSG_TEXT) {
        return true
    }
    if (message.type === MESSAGE_TYPES.MSG_IMAGE) {
        return true
    }
    return false
})

const canRevokeMessage = computed(() => {
    const message = props.message
    if (props.isGroupSession) {
        return false
    }
    return message.flow === 'out' && message.status === 'success'
})

const canQuoteMessage = computed(() => {
    return !props.isGroupSession
})

const canTranslateMessage = computed(() => {
    return props.message.type === MESSAGE_TYPES.MSG_TEXT
})

function closeMenu() {
    emit('close')
}

const fallbackCopy = (text: string) => {
    const textarea = document.createElement('textarea')
    textarea.value = text
    textarea.style.position = 'fixed'
    textarea.style.opacity = '0'
    document.body.appendChild(textarea)
    textarea.select()
    document.execCommand('copy')
    document.body.removeChild(textarea)
}

const copyText = async (text: string) => {
    try {
        if (navigator.clipboard?.writeText) {
            await navigator.clipboard.writeText(text)
        } else {
            fallbackCopy(text)
        }

        toast.success(t('已复制消息文本'))
        closeMenu()
    } catch (error) {
        toast.error(t('复制失败：{reason}', { reason: parseUnknownError(error) }))
    }
}

const copyImageToClipboard = async (imageSource: string | HTMLImageElement | Blob,) => {
    try {
        const activeEl = document.activeElement as HTMLElement | null
        if (activeEl) {
            activeEl.blur()
        }
        document.body.focus({ preventScroll: true })
    } catch (e) {
        console.warn('Failed to focus document:', e)
    }
    try {
        let imageBlob: Blob

        if (imageSource instanceof Blob) {
            imageBlob = imageSource
        } else if (typeof imageSource === 'string' || imageSource instanceof HTMLImageElement) {
            let imgElement: HTMLImageElement

            if (typeof imageSource === 'string') {
                imgElement = new Image()
                imgElement.crossOrigin = 'anonymous'
                imgElement.src = imageSource

                await new Promise<void>((resolve, reject) => {
                    imgElement.onload = () => resolve()
                    imgElement.onerror = (err) => reject(new Error(`Image loading failed: ${err}`))
                })
            } else {
                imgElement = imageSource
            }

            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            if (!ctx) {
                console.error('Failed to copy image: Canvas 2D context acquisition failed')
                return false
            }

            canvas.width = imgElement.width
            canvas.height = imgElement.height
            ctx.drawImage(imgElement, 0, 0)

            imageBlob = await new Promise<Blob>((resolve, reject) => {
                canvas.toBlob(
                    (blob) => {
                        if (!blob) {
                            reject(new Error('Canvas conversion to Blob failed, returned null'))
                            return
                        }
                        resolve(blob)
                    },
                    'image/png',
                    1.0,
                )
            })
        } else {
            console.error(
                'Failed to copy image: Image source must be a URL string, HTMLImageElement or Blob type',
            )
            return false
        }

        const clipboardItem = new ClipboardItem({
            [imageBlob.type || 'image/png']: imageBlob,
        } as Record<string, Blob>)

        await navigator.clipboard.write([clipboardItem])
        toast.success(t('复制图片成功'))
        return true
    } catch (error) {
        throw error
    }
}

const copyMessageContent = async () => {
    const message = props.message

    if (message.type === MESSAGE_TYPES.MSG_IMAGE) {
        try {
            const imageUrl = getMessageOpenUrl(message)
            copyImageToClipboard(imageUrl)
            closeMenu()
        } catch (error) {
            toast.error(t('复制失败：{reason}', { reason: parseUnknownError(error) }))
        }
        return
    }

    await copyText(message.payload?.text ?? '')
}

const revokeMessage = async () => {
    const message = props.message

    try {
        await AIDeskCoreInstance.chatService.revokeMessage(message)
        closeMenu()
    } catch (error) {
        toast.error(t('撤回失败：{reason}', { reason: parseUnknownError(error) }))
    }
}

const quoteMessage = () => {
    const message = props.message
    AIDeskCoreInstance.chatService.quoteMessage({
        messageID: message?.ID,
        messageAbstract: formateMessageAbstract(message, props.currentUserId),
        messageSender: message?.from,
        messageSenderNick: message?.nick,
        messageType: getReplyMessageOriginType(message),
        messageTime: message?.time,
        messageSequence: message?.sequence,
        version: 1,
    })
    closeMenu()
}

const translateMessage = async () => {
    closeMenu()
    await AIDeskCoreInstance.chatService.translateTextMessage({ message: props.message, targetLanguage: messageListTranslateTargetLanguage.value || 'zh' })
}

const actionItems = computed<MenuAction[]>(() => {
    const message = props.message
    const isUserMsg = message?.flow === 'in' && message?.from !== 'administrator'

    return [
        {
            key: 'copy',
            label: t('复制'),
            icon: copyIcon,
            visible: canCopyMessage.value,
            click: copyMessageContent,
        },
        {
            key: 'translate',
            label: t('翻译'),
            icon: translateIcon,
            visible: canTranslateMessage.value,
            disabled: isUserMsg
                ? userMsgTranslationEnable.value === 0
                : seatMsgTranslationEnable.value === 0,
            click: translateMessage,
        },
        {
            key: 'revoke',
            label: t('撤回'),
            icon: revokeIcon,
            visible: canRevokeMessage.value,
            click: revokeMessage,
        },
        {
            key: 'quote',
            label: t('引用'),
            icon: quoteIcon,
            visible: canQuoteMessage.value,
            click: quoteMessage,
        },
    ]
})

const visibleActionItems = computed(() => {
    return actionItems.value.filter((item) => item.visible)
})

const handleActionClick = async (item: MenuAction) => {
    if (item.disabled) {
        return
    }
    await item.click()
}
</script>

<style scoped>
.pc-message-context-menu {
    position: fixed;
    z-index: 1200;
    width: max-content;
    min-width: 0;
    max-width: calc(100vw - 24px);
    max-height: calc(100vh - 24px);
    box-sizing: border-box;
    padding: 6px;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    background: #ffffff;
    border: 1px solid #E6E8EB;
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.12);
    overflow-y: auto;
}

.pc-message-context-menu-item {
    width: 100%;
    min-height: 36px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: #1d2129;
    text-align: left;
    padding: 6px 12px;
    cursor: pointer;
    font-size: 14px;
    line-height: 22px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    transition: background-color 0.15s ease;
}

.pc-message-context-menu-item:hover {
    background: #F2F3F5;
}

.pc-message-context-menu-item:active {
    background: #E6E9F0;
}

.pc-message-context-menu-item:disabled {
    cursor: not-allowed;
    opacity: 0.4;
}

.pc-message-context-menu-item:disabled:hover,
.pc-message-context-menu-item:disabled:active {
    background: transparent;
}

.pc-message-context-menu-item__icon {
    width: 18px;
    height: 18px;
    display: block;
    flex-shrink: 0;
    /* 原图标为白色，PC 白底菜单下将其变为黑色 */
    filter: brightness(0);
}

.pc-message-context-menu-item__label {
    color: inherit;
    white-space: nowrap;
}
</style>
