<template>
    <div class="pc-chat-input">
        <InputToolbar ref="toolbarRef" :session-id="sessionId" :is-inner-session="isInnerSession"
            :get-source-text="() => draftMessage"
            @select-emoji="handleSelectEmoji" @append-text="handleAppendQuickReplyText"
            @insert-media="handleInsertQuickReplyMedia" @use-text="handleUseText" @translated="handleTranslated"
            @panel-open="handlePanelOpen" />

        <div v-if="translationResult" class="pc-translation-result">
            <div class="pc-translation-result-text">{{ translationResult }}</div>
            <button class="pc-translation-result-use" type="button" @click="handleUseTranslation">
                {{ t('使用') }}
                <span class="pc-translation-result-use-icon" aria-hidden="true">
                    <svg viewBox="0 0 12 12" width="12" height="12" xmlns="http://www.w3.org/2000/svg">
                        <path d="M2 4l4 3.2L10 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"
                            stroke-linejoin="round" fill="none" />
                        <path d="M2 7.2l4 3.2 4-3.2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"
                            stroke-linejoin="round" fill="none" />
                    </svg>
                </span>
            </button>
            <button class="pc-translation-result-close" type="button" :aria-label="t('关闭')"
                @click="handleCloseTranslation">×</button>
        </div>

        <div v-if="currentQuoteMessage" class="pc-quote">
            <div class="pc-quote-content">
                <span class="pc-quote-sender">{{ quoteMessageSender }}:</span>
                <span class="pc-quote-abstract">{{ quoteMessageAbstract }}</span>
            </div>
            <button class="pc-quote-close" type="button" @click.stop="handleClearQuoteMessage">×</button>
        </div>

        <div :class="['pc-input-editor-wrapper', { 'is-disabled': isTextPolishing }]" @click="focusEditor"
            @drop.prevent="onDrop" @paste="onPaste" @dragover.prevent>
            <EditorContent :editor="editor" class="pc-input-editor-content" />
        </div>

        <div class="pc-input-footer">
            <PolishChip :session-id="sessionId" :get-source-text="() => draftMessage" @polished="handleUseText"
                @polishing-change="handlePolishingChange" />

            <button class="pc-send-button" type="button" :disabled="!canSend" @click="handleSend">
                {{ t('发送') }}
            </button>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { EditorContent, useEditor } from '@tiptap/vue-3';
import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
import AIDeskCoreInstance, {
    CUSTOM_MESSAGE_SRC,
    StoreName,
} from '@tencentcloud/ai-desk-core';
import 'tippy.js/dist/tippy.css';
import toast from '../../../../utils/toast';
import { decodeText } from '../../../../utils/decodeText';
import CustomImage from './messageInputFile';
import { QuickReplyExtension, quickReplySuggestionPluginKey } from './QuickReply/extension';
import quickReplySuggestion from './QuickReply/suggestion';
import InputToolbar from './InputToolbar/index.vue';
import PolishChip from './PolishChip.vue';

interface ChatInputProps {
    sessionId: string;
    isInnerSession?: boolean;
}

interface EmojiSelectionData {
    url: string;
    emoji: { key: string };
}

interface EditorNode {
    type?: string;
    text?: string;
    attrs?: {
        alt?: string;
        title?: string;
        src?: string;
        class?: string;
        originUrl?: string;
    };
    content?: EditorNode[];
}

type SendSegmentType = 'text' | 'image' | 'video' | 'file' | 'richText';

interface SendSegment {
    type: SendSegmentType;
    text?: string;
    file?: File;
    richTextContent?: string;
}

interface DecodedTextItem {
    name: 'text' | 'img';
    text?: string;
    src?: string;
    key?: string;
}

interface EditorTextNode {
    type: 'text';
    text: string;
}

interface EditorHardBreakNode {
    type: 'hardBreak';
}

interface EditorImageNode {
    type: 'custom-image';
    attrs: { src: string; alt: string; title: string; class: string; originUrl?: string };
}

type EditorInlineNode = EditorTextNode | EditorHardBreakNode | EditorImageNode;

const props = defineProps<ChatInputProps>();
const { t, locale } = useI18n();

const draftMessage = ref('');
const hasContent = ref(false);
const isSending = ref(false);
const currentQuoteMessage = ref<{
    messageID: string,
    messageAbstract: string,
    messageSender: string,
    messageSenderNick: string,
    messageType: number,
    messageTime: number,
    messageSequence: number,
    version: 1,
} | undefined>(undefined);
const isTextPolishing = ref(false);
const toolbarRef = ref<InstanceType<typeof InputToolbar> | null>(null);
const translationResult = ref('');

// 缓存拖拽/粘贴/快捷回复插入的原始 File（key 为 blobUrl 或 dataUrl）
const fileMap = new Map<string, File>();

const blockNodeTypeSet = new Set(['paragraph', 'heading', 'blockquote', 'codeBlock', 'listItem']);

const serializeEditorNodes = (nodes: EditorNode[] = []): string => nodes
    .map((node, index) => {
        const content = serializeEditorNode(node);
        if (!content) {
            return '';
        }

        if (blockNodeTypeSet.has(node.type ?? '') && index < nodes.length - 1) {
            return `${content}\n`;
        }

        return content;
    })
    .join('');

const serializeEditorNode = (node: EditorNode): string => {
    if (node.type === 'text') {
        return node.text ?? '';
    }

    if (node.type === 'hardBreak') {
        return '\n';
    }

    if (node.type === 'custom-image') {
        // 只有表情才作为文本模拟（供 PolishChip/Translate 拿源文本）；图片/文件不参与
        if (node.attrs?.class === 'Emoji') {
            return node.attrs?.alt ?? node.attrs?.title ?? '';
        }
        return '';
    }

    return serializeEditorNodes(node.content ?? []);
};

const syncDraftMessage = (currentEditor?: { getJSON: () => unknown } | null) => {
    if (!currentEditor) {
        draftMessage.value = '';
        hasContent.value = false;
        return;
    }

    const json = currentEditor.getJSON() as EditorNode;
    draftMessage.value = serializeEditorNode(json);
    hasContent.value = getEditorSegments(json).length > 0;
};

// —— 生成编辑器发送段落（拆分为 text / image / video / file / richText 顺序数组） —— //
const getEditorSegments = (doc: EditorNode): SendSegment[] => {
    const segments: SendSegment[] = [];
    let textBuffer = '';

    const flushText = () => {
        const trimmed = textBuffer.replace(/\s+$/g, '');
        if (trimmed) {
            segments.push({ type: 'text', text: trimmed });
        }
        textBuffer = '';
    };

    const walk = (node: EditorNode) => {
        if (!node) return;
        if (node.type === 'text') {
            textBuffer += node.text ?? '';
            return;
        }
        if (node.type === 'hardBreak') {
            textBuffer += '\n';
            return;
        }
        if (node.type === 'custom-image') {
            const cls = node.attrs?.class || 'Normal';
            const src = node.attrs?.src || '';
            const originUrl = node.attrs?.originUrl || '';

            if (cls === 'Emoji') {
                // 表情按文本处理，走 alt/title
                textBuffer += node.attrs?.alt ?? node.attrs?.title ?? '';
                return;
            }

            // 遇到媒体节点，先把已有文本刷出去
            flushText();

            // 快捷回复插入的图片/视频：带 originUrl，统一按 richText 发送
            if (originUrl) {
                if (cls === 'File') {
                    // 视频快捷回复
                    segments.push({
                        type: 'richText',
                        richTextContent: `<video src="${originUrl}" controls></video>`,
                    });
                } else {
                    // 图片快捷回复：使用 Markdown 语法
                    segments.push({
                        type: 'richText',
                        richTextContent: `![](${originUrl})`,
                    });
                }
                return;
            }

            const cachedFile = src ? fileMap.get(src) : undefined;

            if (cls === 'File') {
                // 拖拽/粘贴的非图片文件
                if (cachedFile) {
                    if (cachedFile.type.startsWith('video/')) {
                        segments.push({ type: 'video', file: cachedFile });
                    } else {
                        segments.push({ type: 'file', file: cachedFile });
                    }
                }
                return;
            }

            // 拖拽/粘贴的普通图片
            if (cachedFile) {
                segments.push({ type: 'image', file: cachedFile });
            }
            return;
        }
        if (node.type === 'paragraph') {
            if (Array.isArray(node.content)) {
                node.content.forEach(walk);
            }
            // 段落间用换行分隔
            textBuffer += '\n';
            return;
        }
        if (Array.isArray(node.content)) {
            node.content.forEach(walk);
        }
    };

    if (Array.isArray(doc.content)) {
        doc.content.forEach(walk);
    }
    flushText();

    return segments;
};

const buildInlineContentFromDecodedItems = (items: DecodedTextItem[]): EditorInlineNode[] => {
    const nodes: EditorInlineNode[] = [];

    items.forEach((item) => {
        if (item.name === 'img' && item.src && item.key) {
            nodes.push({
                type: 'custom-image',
                attrs: {
                    src: item.src,
                    alt: item.key,
                    title: item.key,
                    class: 'Emoji',
                },
            });
            return;
        }

        if (item.name === 'text' && item.text) {
            nodes.push({ type: 'text', text: item.text });
        }
    });

    return nodes;
};

const buildInlineContentFromText = (text: string): EditorInlineNode[] => {
    const lines = text.split('\n');

    return lines.flatMap((line, index) => {
        const nodes: EditorInlineNode[] = buildInlineContentFromDecodedItems(
            decodeText({ text: line }) as DecodedTextItem[],
        );

        if (index < lines.length - 1) {
            nodes.push({ type: 'hardBreak' });
        }

        return nodes;
    });
};

const buildEditorContentFromText = (text: string) => ({
    type: 'doc',
    content: [
        {
            type: 'paragraph',
            content: buildInlineContentFromText(text),
        },
    ],
});

const applyReEditText = async (text = '') => {
    if (!editor.value) {
        return;
    }

    editor.value.commands.setContent(buildEditorContentFromText(text));
    syncDraftMessage(editor.value);
    toolbarRef.value?.closeAllPanels();

    await nextTick();
    editor.value.commands.focus('end');
};

const appendText = async (text = '') => {
    if (!editor.value || !text) {
        return;
    }

    editor.value.chain().focus('end').insertContent(buildInlineContentFromText(text)).run();
    syncDraftMessage(editor.value);
    toolbarRef.value?.closeAllPanels();

    await nextTick();
    editor.value.commands.focus('end');
};

defineExpose({ setText: applyReEditText });

const editor = useEditor({
    content: '',
    extensions: [
        StarterKit,
        CustomImage.configure({ inline: true, allowBase64: true }),
        QuickReplyExtension.configure({ suggestion: quickReplySuggestion() as any }),
        Placeholder.configure({
            placeholder: () => t('“Enter”键发送，“Shift+Enter”键换行，“#”键快捷回复'),
            showOnlyWhenEditable: true,
            showOnlyCurrent: false,
        }),
    ],
    editorProps: {
        attributes: {
            class: 'pc-input-editor',
        },
        handleKeyDown: (view, event) => {
            if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) {
                // 快捷回复面板打开时，Enter 交给 suggestion 处理，不发送消息
                const suggestionState = quickReplySuggestionPluginKey.getState(view.state);
                if (suggestionState?.active) {
                    return false;
                }
                event.preventDefault();
                void handleSend();
                return true;
            }
            return false;
        },
        handleDOMEvents: {
            focus: () => {
                toolbarRef.value?.closeAllPanels();
                return false;
            },
        },
    },
    onCreate: ({ editor: currentEditor }) => {
        syncDraftMessage(currentEditor);
        // 监听 QuickReplyExtension 派发的图片/视频事件
        (currentEditor as any).on('insertImage', (data: { src: string; thumbImage: string }) => {
            void insertQuickReplyMedia(data.src, 'image', data.thumbImage);
        });
        (currentEditor as any).on('insertVideo', (data: { src: string; thumbImage: string }) => {
            void insertQuickReplyMedia(data.src, 'video', data.thumbImage);
        });
    },
    onUpdate: ({ editor: currentEditor }) => syncDraftMessage(currentEditor),
});

// placeholder 文案由 t() 生成，tiptap 只在事务更新时才会重算。
// 切换语言时派发一个空事务，强制重绘 placeholder 装饰。
watch(locale, () => {
    const view = editor.value?.view;
    if (view) {
        view.dispatch(view.state.tr);
    }
});

const hasText = computed(() => Boolean(draftMessage.value.trim()));
const canSend = computed(() => (hasText.value || hasContent.value) && !isSending.value && !isTextPolishing.value);

// —— 拖拽/粘贴：把 File 转成 CustomImage 节点插入编辑器 —— //
// 文件图标缓存
const fileIconDomMap = new Map<string, HTMLImageElement>();

const handleNameForShow = (value: string) => {
    if (!value) {
        return value;
    }
    let res = '';
    let length = 0;
    for (let i = 0; i < value.length; i++) {
        if (length > 16) {
            res += '...';
            break;
        }
        const ch = value[i];
        if (!ch) continue;
        res += ch;
        if (/[a-z]|[0-9]|[,;.!@#\-+/\\$%^*()<>?:"'{}~]/i.test(ch)) {
            length += 1;
        } else {
            length += 2;
        }
    }
    return res;
};

const handleFileIconForShow = (type: string) => {
    const urlBase = 'https://web.sdk.qcloud.com/component/TUIKit/assets/file-';
    const fileTypes = [
        'image',
        'pdf',
        'text',
        'ppt',
        'presentation',
        'sheet',
        'zip',
        'word',
        'video',
        'unknown',
    ];
    let url = '';
    let iconType = '';
    fileTypes.forEach((typeName) => {
        if (type.includes(typeName)) {
            url = `${urlBase + typeName}.svg`;
            iconType = typeName;
        }
    });
    return {
        iconSrc: url ? url : `${urlBase}unknown.svg`,
        iconType: iconType ? iconType : 'unknown',
    };
};

const addImageProcess = (src: string, type: string): Promise<HTMLImageElement> => new Promise((resolve, reject) => {
    if (fileIconDomMap.has(type)) {
        resolve(fileIconDomMap.get(type) as HTMLImageElement);
        return;
    }
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
        fileIconDomMap.set(type, img);
        resolve(img);
    };
    img.onerror = reject;
    img.src = src;
});

const drawFileCanvasToImageUrl = async (file: { name: string; type: string }): Promise<string> => {
    const { name, type } = file;
    const canvas = document.createElement('canvas');
    const width = 160;
    const height = 50;
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    const scale = window.devicePixelRatio || 1;
    canvas.width = Math.floor(width * scale);
    canvas.height = Math.floor(height * scale);
    const ctx = canvas.getContext('2d');
    if (!ctx) {
        return '';
    }
    ctx.scale(scale, scale);
    const { iconSrc, iconType } = handleFileIconForShow(type);
    try {
        const img = await addImageProcess(iconSrc, iconType);
        ctx.drawImage(img, 10, 10, 30, 30);
    } catch (err) {
        console.error('load file icon fail', err);
    }
    const nameForShow = handleNameForShow(name);
    ctx.fillStyle = '#1D2129';
    ctx.font = '12px sans-serif';
    ctx.fillText(nameForShow, 45, 22);
    return canvas.toDataURL();
};

const insertFileToEditor = async (file: File) => {
    if (!editor.value || isTextPolishing.value) return;
    const isImage = file.type.startsWith('image/');
    const src = isImage ? URL.createObjectURL(file) : await drawFileCanvasToImageUrl(file);
    if (!src) return;
    fileMap.set(src, file);
    editor.value
        .chain()
        .focus()
        .insertContent({
            type: 'custom-image',
            attrs: {
                src,
                alt: file.name,
                title: file.name,
                class: isImage ? 'Normal' : 'File',
            },
        })
        .run();
};

const onDrop = (event: DragEvent) => {
    const files = event.dataTransfer?.files;
    if (!files || !files.length) return;
    Array.from(files).forEach((f) => { void insertFileToEditor(f); });
};

const onPaste = (event: ClipboardEvent) => {
    const items = event.clipboardData?.items;
    if (!items || !items.length) return;
    const files: File[] = [];
    Array.from(items).forEach((item) => {
        if (item.kind === 'file') {
            const f = item.getAsFile();
            if (f) files.push(f);
        }
    });
    if (files.length) {
        event.preventDefault();
        files.forEach((f) => { void insertFileToEditor(f); });
    }
};

// —— 快捷回复的图片/视频：直接用 originUrl 展示预览，发送时统一走 richText —— //
const insertQuickReplyMedia = async (
    contentUrl: string,
    type: 'image' | 'video',
    _thumbImage: string,
) => {
    if (!editor.value || !contentUrl) return;
    try {
        if (type === 'image') {
            editor.value
                .chain()
                .focus()
                .insertContent({
                    type: 'custom-image',
                    attrs: {
                        src: contentUrl,
                        alt: contentUrl,
                        title: contentUrl,
                        class: 'Normal',
                        originUrl: contentUrl,
                    },
                })
                .run();
            return;
        }
        // 视频：统一按文件卡片样式展示（图标 + "视频"名字），不使用缩略图，避免拉伸难看
        const displayName = t('视频');
        const src = await drawFileCanvasToImageUrl({ name: displayName, type: 'video/quickreply' });
        editor.value
            .chain()
            .focus()
            .insertContent({
                type: 'custom-image',
                attrs: {
                    src,
                    alt: displayName,
                    title: displayName,
                    class: 'File',
                    originUrl: contentUrl,
                },
            })
            .run();
    } catch (err) {
        console.error(err);
        toast.error(t('插入快捷回复失败'));
    }
};

const focusEditor = () => {
    if (isTextPolishing.value) {
        return;
    }

    editor.value?.commands.focus();
};

// —— 工具栏事件 —— //
const handleSelectEmoji = (data: EmojiSelectionData) => {
    if (isTextPolishing.value) {
        return;
    }

    editor.value?.commands?.insertContent({
        type: 'custom-image',
        attrs: {
            src: data.url,
            alt: data.emoji.key,
            title: data.emoji.key,
            class: 'Emoji',
        },
    });
};

const handleAppendQuickReplyText = async (text: string) => {
    if (isTextPolishing.value || !text) {
        return;
    }

    await appendText(text);
};

const handleInsertQuickReplyMedia = async (payload: {
    type: 'image' | 'video';
    content: string;
    thumbImage: string;
}) => {
    if (isTextPolishing.value) {
        return;
    }
    await insertQuickReplyMedia(payload.content, payload.type, payload.thumbImage);
    toolbarRef.value?.closeAllPanels();
};

const handleUseText = async (text: string) => {
    if (!text) {
        return;
    }
    await applyReEditText(text);
};

const handleTranslated = (text: string) => {
    translationResult.value = text || '';
};

const handleUseTranslation = async () => {
    if (!translationResult.value) {
        return;
    }
    await applyReEditText(translationResult.value);
    translationResult.value = '';
};

const handleCloseTranslation = () => {
    translationResult.value = '';
};

const handlePanelOpen = () => {
    editor.value?.commands.blur();
};

// —— 文本润色 —— //
const handlePolishingChange = (polishing: boolean) => {
    isTextPolishing.value = polishing;
    editor.value?.setEditable(!polishing);
    if (polishing) {
        editor.value?.commands.blur();
        toolbarRef.value?.closeAllPanels();
    }
};

// —— 引用消息 —— //
const quoteMessageSender = computed(() => {
    return currentQuoteMessage.value?.messageSenderNick?.trim() || currentQuoteMessage.value?.messageSender || '';
});

const quoteMessageAbstract = computed(() => {
    if (!currentQuoteMessage.value) {
        return '';
    }
    return currentQuoteMessage.value.messageAbstract || '';
});

const handleClearQuoteMessage = () => {
    AIDeskCoreInstance.chatService.quoteMessage(undefined);
};

// —— 发送 —— //
const sendSingleSegment = async (segment: SendSegment) => {
    if (!props.sessionId) return;
    switch (segment.type) {
        case 'text': {
            const text = (segment.text || '').trim();
            if (!text) return;
            await AIDeskCoreInstance.chatService.sendTextMessage({
                sessionId: props.sessionId,
                payload: { text },
            });
            return;
        }
        case 'image': {
            if (!segment.file) return;
            await AIDeskCoreInstance.chatService.sendImageMessage({
                sessionId: props.sessionId,
                payload: { file: segment.file },
            });
            return;
        }
        case 'video': {
            if (!segment.file) return;
            await AIDeskCoreInstance.chatService.sendVideoMessage({
                sessionId: props.sessionId,
                payload: { file: segment.file },
            });
            return;
        }
        case 'file': {
            if (!segment.file) return;
            await AIDeskCoreInstance.chatService.sendFileMessage({
                sessionId: props.sessionId,
                payload: { file: segment.file },
            });
            return;
        }
        case 'richText': {
            if (!segment.richTextContent) return;
            await AIDeskCoreInstance.chatService.sendCustomMessage({
                payload: {
                    data: JSON.stringify({
                        src: CUSTOM_MESSAGE_SRC.RICH_TEXT,
                        content: segment.richTextContent,
                        customerServicePlugin: 0,
                    }),
                },
            });
        }
    }
};

const handleSend = async () => {
    if (!editor.value || !props.sessionId || isSending.value || isTextPolishing.value) {
        return;
    }

    const doc = editor.value.getJSON() as EditorNode;
    const segments = getEditorSegments(doc);
    if (!segments.length) return;

    try {
        isSending.value = true;
        editor.value.commands.clearContent(true);
        // 严格串行：await 上一次完成再发下一条
        for (const segment of segments) {
            // eslint-disable-next-line no-await-in-loop
            await sendSingleSegment(segment);
        }
        // 清空并释放 blobUrl
        fileMap.forEach((_file, url) => {
            if (url.startsWith('blob:')) {
                URL.revokeObjectURL(url);
            }
        });
        fileMap.clear();
        syncDraftMessage(editor.value);
        toolbarRef.value?.closeAllPanels();
    } catch (error) {
        console.error(error);
        toast.error(t('发送失败'));
    } finally {
        isSending.value = false;
    }
};

// —— store 订阅 —— //
const onQuoteMessageUpdated = (message: {
    messageID: string,
    messageAbstract: string,
    messageSender: string,
    messageSenderNick: string,
    messageType: number,
    messageTime: number,
    messageSequence: number,
    version: 1,
} | undefined) => {
    currentQuoteMessage.value = message;
};

onMounted(() => {
    AIDeskCoreInstance.coreStore.watch(StoreName.CHAT, {
        quoteMessage: onQuoteMessageUpdated,
    });
});

onUnmounted(() => {
    AIDeskCoreInstance.coreStore.unwatch(StoreName.CHAT, {
        quoteMessage: onQuoteMessageUpdated,
    });
});

onBeforeUnmount(() => {
    fileMap.forEach((_file, url) => {
        if (url.startsWith('blob:')) {
            URL.revokeObjectURL(url);
        }
    });
    fileMap.clear();
    editor.value?.destroy();
});
</script>

<style scoped>
.pc-chat-input {
    position: relative;
    flex-shrink: 0;
    padding: 12px 20px 20px;
    background: #fff;
    border-top: 1px solid #f0f0f0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.pc-input-editor-wrapper {
    flex: 1;
    min-height: 120px;
    max-height: 220px;
    overflow-y: auto;
    padding: 4px 0;
    cursor: text;
}

.pc-input-editor-wrapper.is-disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.pc-input-editor-content {
    min-height: 100%;
}

.pc-input-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding-top: 4px;
}

.pc-send-button {
    height: 36px;
    min-width: 88px;
    padding: 0 24px;
    border: none;
    border-radius: 18px;
    background: #1a6dff;
    color: #fff;
    font-size: 14px;
    line-height: 20px;
    cursor: pointer;
}

.pc-send-button:disabled {
    background: #b8c9ee;
    cursor: not-allowed;
}

.pc-quote {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border-radius: 8px;
    background: #F0F2F7;
}

.pc-quote-content {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    overflow: hidden;
    font-size: 12px;
    line-height: 16px;
}

.pc-quote-sender {
    max-width: 40%;
    color: #676A70;
    font-weight: 500;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.pc-quote-abstract {
    flex: 1;
    color: #676A70;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.pc-quote-close {
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    background: transparent;
    color: #676A70;
    font-size: 16px;
    cursor: pointer;
}

.pc-translation-result {
    position: absolute;
    left: 20px;
    right: 20px;
    bottom: calc(100% + 8px);
    z-index: 10;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    max-height: 240px;
    padding: 12px 14px;
    border-radius: 10px;
    background: #fff;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12);
    box-sizing: border-box;
}

.pc-translation-result-text {
    flex: 1;
    min-width: 0;
    color: #222;
    font-size: 14px;
    line-height: 22px;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 216px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
}

.pc-translation-result-text::-webkit-scrollbar {
    width: 4px;
}

.pc-translation-result-text::-webkit-scrollbar-track {
    background: transparent;
}

.pc-translation-result-text::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.15);
    border-radius: 4px;
}

.pc-translation-result-text::-webkit-scrollbar-thumb:hover {
    background: rgba(0, 0, 0, 0.25);
}

.pc-translation-result-use {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 24px;
    padding: 0;
    border: none;
    background: transparent;
    color: #1a6dff;
    font-size: 14px;
    line-height: 22px;
    cursor: pointer;
    flex-shrink: 0;
}

.pc-translation-result-use-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    color: #1a6dff;
}

.pc-translation-result-close {
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    background: transparent;
    color: #676A70;
    font-size: 18px;
    line-height: 18px;
    cursor: pointer;
    flex-shrink: 0;
}

:deep(.pc-input-editor) {
    min-height: 100px;
    color: #222;
    font-size: 14px;
    line-height: 22px;
    word-break: break-word;
    white-space: pre-wrap;
    outline: none;
}

:deep(.pc-input-editor p) {
    margin: 0;
    white-space: pre-wrap;
}

:deep(.pc-input-editor p.is-editor-empty:first-child::before) {
    content: attr(data-placeholder);
    float: left;
    height: 0;
    color: #a5a9b0;
    font-size: 14px;
    line-height: 22px;
    pointer-events: none;
}

:deep(.pc-input-editor img.custom-image-Emoji) {
    width: 20px;
    height: 20px;
    vertical-align: text-bottom;
}

:deep(.pc-input-editor img.custom-image-Normal) {
    max-width: 160px;
    max-height: 120px;
    border-radius: 6px;
    vertical-align: middle;
    margin: 2px;
}

:deep(.pc-input-editor img.custom-image-File) {
    width: 160px;
    height: 50px;
    border: 1px solid #e8e8e9;
    border-radius: 5px;
    vertical-align: middle;
    margin: 2px;
}
</style>

<style>
/* 快捷回复 tippy 主题：去掉默认黑色气泡背景 */
.tippy-box[data-theme~='qr-suggest'] {
    background: transparent;
    box-shadow: none;
    color: inherit;
}

.tippy-box[data-theme~='qr-suggest']>.tippy-content {
    padding: 0;
    background: transparent;
}

.tippy-box[data-theme~='qr-suggest']>.tippy-svg-arrow,
.tippy-box[data-theme~='qr-suggest']>.tippy-arrow {
    display: none;
}
</style>
