<template>
    <div :class="['chat-input-bar', { 'is-voice-record-overlay-active': showVoiceRecordOverlay }]">
        <div v-if="floatingActions.length" class="chat-floating-entry-list">
            <button v-for="item in floatingActions" :key="item.key" class="chat-floating-entry-button" type="button"
                :disabled="item.disabled?.()" @click="item.click()">
                <img v-if="item.icon" :src="item.isLoading?.() ? (item.loadingIcon ?? item.icon) : item.icon"
                    :class="['chat-floating-entry-button-icon', { 'is-loading': item.isLoading?.() }]"
                    :alt="item.label" />
                <span>{{ item.label }}</span>
            </button>
        </div>

        <div v-if="translatedText && showTranslationLanguagePanel" class="chat-translation-language-list">
            <button v-for="item in translationLanguageOptions" :key="item.key"
                :class="['chat-translation-language-option', { 'is-active': item.key === selectedTranslationLanguage }]"
                type="button" @click="handleSelectTranslationLanguage(item.key)">
                {{ item.label }}
            </button>
        </div>

        <div v-if="translatedText" class="chat-translation-preview">
            <button class="chat-translation-language-button" type="button"
                @click="handleToggleTranslationLanguagePanel">
                <span>{{ selectedTranslationLanguageLabel }}</span>
                <span class="chat-translation-language-arrow"></span>
            </button>
            <div class="chat-translation-content">{{ translatedText }}</div>
            <button class="chat-translation-use-button" type="button" @click="handleUseTranslation">
                {{ t('使用') }}
            </button>
            <button class="chat-translation-close-button" type="button" :aria-label="t('关闭翻译结果')"
                @click="handleCloseTranslation">
                ×
            </button>
        </div>

        <div v-if="aiResponseText" class="chat-ai-response-preview">
            <div class="chat-ai-response-content">{{ aiResponseText }}</div>
            <button class="chat-ai-response-use-button" type="button" :disabled="isAIResponseLoading"
                @click="handleUseAIResponse">
                {{ t('使用') }}
            </button>
            <button class="chat-ai-response-close-button" type="button" :aria-label="t('关闭智能回复')"
                @click="handleCloseAIResponse">
                ×
            </button>
        </div>

        <div class="chat-input-shell">
            <div v-if="currentQuoteMessage" class="chat-input-quote">
                <div class="chat-input-quote-content">
                    <span class="chat-input-quote-sender">{{ quoteMessageSender }}:</span>
                    <span class="chat-input-quote-abstract">{{ quoteMessageAbstract }}</span>
                </div>
                <button class="chat-input-quote-close" type="button" @click.stop="handleClearQuoteMessage">
                    <img :src="closeQuoteIcon" class="chat-input-quote-close-icon" :alt="t('取消引用')" />
                </button>
            </div>

            <div class="chat-input-main">
                <button class="chat-icon-button chat-mode-button" type="button" :disabled="isTextPolishing"
                    @click="handleToggleVoiceInputMode">
                    <img :src="showVoiceInputMode ? switchKeyboardIcon : sendAudioIcon" class="chat-icon-image"
                        :alt="showVoiceInputMode ? t('切换键盘') : t('发送语音')" />
                </button>

                <button v-if="showVoiceInputMode" class="chat-voice-trigger" type="button"
                    :disabled="isSending || isVoiceRecordPreparing || isTextPolishing"
                    @pointerdown.prevent="handleVoiceRecordPointerDown"
                    @pointermove.prevent="handleVoiceRecordPointerMove" @pointerup.prevent="handleVoiceRecordPointerUp"
                    @pointercancel.prevent="handleVoiceRecordPointerCancel" @contextmenu.prevent @dragstart.prevent
                    @selectstart.prevent>
                    {{ t('按住说话') }}
                </button>

                <div v-else :class="['chat-input-editor-container', { 'is-disabled': isTextPolishing }]"
                    @click="focusEditor">
                    <span v-if="!hasText" class="chat-input-placeholder">{{ t('请输入消息') }}</span>
                    <EditorContent :editor="editor" class="chat-input-editor-content" />
                </div>

                <div class="chat-input-actions">
                    <button v-if="!showVoiceInputMode" class="chat-icon-button" type="button"
                        :disabled="isTextPolishing" @click="handleToggleEmojiPanel">
                        <img :src="showEmojiPanel ? switchKeyboardIcon : openEmojiIcon" class="chat-icon-image"
                            :alt="showEmojiPanel ? t('切换键盘') : t('打开表情')" />
                    </button>

                    <button class="chat-icon-button chat-action-button" type="button" :disabled="isTextPolishing"
                        @click="handleActionButtonClick">
                        <img :src="actionButtonIcon" class="chat-icon-image" :alt="actionButtonAlt" />
                    </button>
                </div>
            </div>
        </div>

        <InputEmoji v-if="showEmojiPanel" @select="handleSelectEmoji" @delete-character="handleDeleteCharacter" />
        <InputTools v-show="showInputToolsPanel && !showEmojiPanel" ref="inputToolsRef" :session-id="sessionId"
            :is-inner-session="isInnerSession" @sent="handleInputToolSent"
            @append-text="handleAppendQuickReplyText" />

        <div v-if="showVoiceRecordOverlay" class="chat-voice-record-overlay">
            <div class="chat-voice-record-card">
                <img :src="voiceRecordOverlayBackground" class="chat-voice-record-card-background" alt="" />
                <div class="chat-voice-record-card-content">
                    <div :class="['chat-voice-record-tip', { 'is-cancel': isVoiceRecordCancelReady }]">
                        {{ voiceRecordTipText }}
                    </div>
                    <img :src="voiceRecordOverlayIcon" class="chat-voice-record-wave" alt="" />
                </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { EditorContent, useEditor } from '@tiptap/vue-3';
import StarterKit from '@tiptap/starter-kit';
import Recorder from 'js-audio-recorder';
import { v4 as uuidv4 } from 'uuid';
import AIDeskCoreInstance, { AI_DESK_EVENTS, StoreName, type AIDeskEventParamsMap } from '@tencentcloud/ai-desk-core';
import toast from '../../../../utils/toast';
import { decodeText } from '../../../../utils/decodeText';
import closeInputActionsIcon from './assets/closeInputActions.svg';
import closeQuoteIcon from './assets/closeQuote.svg';
import messageSendIcon from './assets/messageSend.svg';
import openEmojiIcon from './assets/openEmoji.svg';
import openInputActionsIcon from './assets/openInputActions.svg';
import aiResponseIcon from './assets/AIResponse.svg';
import sendAudioBackIcon from './assets/sendAudioBack.svg';
import sendAudioCancelBackIcon from './assets/sendAudioCancelBack.svg';
import sendAudioCancelIcon from './assets/sendAudioCancel.svg';
import sendAudioIcon from './assets/sendAudio.svg';
import sendAudioRecordingIcon from './assets/sendAudioRecording.svg';
import switchKeyboardIcon from './assets/switchKeyboard.svg';
import textPolishIcon from './assets/textPolish.svg';
import textGeneratingIcon from './assets/textGenerating.png';
import translateIcon from './assets/translate.svg';
import InputEmoji from './inputEmoji/index.vue';
import InputTools from './inputTools/index.vue';
import CustomImage from './messageInputFile';

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

interface ChatFloatingAction {
    key: string;
    label: string;
    icon?: string;
    loadingIcon?: string;
    isLoading?: () => boolean;
    disabled?: () => boolean;
    click: () => void | Promise<void>;
}

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

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

interface SerializableEditor {
    getJSON: () => unknown;
}

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

interface EditorHardBreakNode {
    type: 'hardBreak';
}

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

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

interface TranslationLanguageOption {
    key: string;
    label: string;
}

type EditorInlineNode = EditorTextNode | EditorHardBreakNode | EditorImageNode;

interface VoiceMessageFile extends File {
    uri?: string;
    duration?: number;
}

const VOICE_RECORD_CANCEL_DISTANCE = 72;
const VOICE_RECORD_FILE_TYPE = 'audio/wav';
const props = defineProps<ChatInputProps>();
const router = useRouter();
const { t } = useI18n();

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

const draftMessage = ref('');
const isSending = ref(false);
const showEmojiPanel = ref(false);
const showInputToolsPanel = ref(false);
const showVoiceInputMode = ref(false);
const inputToolsRef = ref<InstanceType<typeof InputTools> | null>(null);
const currentQuoteMessage = ref<{
    messageID: string,
    messageAbstract: string,
    messageSender: string,
    messageSenderNick: string,
    messageType: number,
    messageTime: number,
    messageSequence: number,
    version: 1,
} | undefined>(undefined);
const voiceRecorder = ref<Recorder | null>(null);
const showVoiceRecordOverlay = ref(false);
const isVoiceRecordPreparing = ref(false);
const isVoiceRecording = ref(false);
const isVoiceRecordCancelReady = ref(false);
const isVoiceRecordPointerPressed = ref(false);
const voiceRecordStartedAt = ref(0);
const voiceRecordPointerStartY = ref(0);
const voiceRecordPointerId = ref<number | null>(null);
const voiceRecordTriggerElement = ref<HTMLElement | null>(null);
const isTextPolishing = ref(false);
const currentTextPolishEnhanceId = ref('');
const msgEnhanceEnable = ref(0);
const seatMsgTranslationEnable = ref(0);
const generateReplyMsgEnable = ref(0);
const translatedText = ref('');
const selectedTranslationLanguage = ref('zh');
const showTranslationLanguagePanel = ref(false);
const isAIResponseLoading = ref(false);
const currentAIResponseReplyId = ref('');
const aiResponseText = ref('');
let voiceRecordSessionId = 0;
let isVoiceRecordWindowEventsBound = false;

const getSelectedTranslationLanguage = (data?: Record<string, string>) => {
    if (!props.sessionId) {
        return 'zh';
    }

    return data?.[props.sessionId] || 'zh';
};

const setTextInputEditable = (editable: boolean) => {
    editor.value?.setEditable(editable);
};

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

    const msgContent = draftMessage.value;
    if (!msgContent.trim()) {
        toast.info(t('没有文本'));
        return;
    }

    const enhanceId = uuidv4();
    currentTextPolishEnhanceId.value = enhanceId;
    isTextPolishing.value = true;
    showEmojiPanel.value = false;
    showInputToolsPanel.value = false;
    showVoiceInputMode.value = false;
    editor.value?.commands.blur();
    setTextInputEditable(false);

    try {
        const polishResult = await AIDeskCoreInstance.chatService.AIMessagePolish({
            sessionId: props.sessionId,
            msgContent,
            enhanceId,
        });

        if (currentTextPolishEnhanceId.value !== enhanceId || polishResult?.enhanceId !== enhanceId) {
            return;
        }
        setTextInputEditable(true);
        await applyReEditText(polishResult?.enhancedMsgContent ?? '');
    } catch (error) {
        console.error(error);
        toast.error(t('润色失败'));
    } finally {
        currentTextPolishEnhanceId.value = '';
        isTextPolishing.value = false;
        setTextInputEditable(true);
    }
};

const handleTranslate = async () => {
    const sourceText = draftMessage.value;
    if (!sourceText.trim()) {
        toast.info(t('没有文本'));
        return;
    }

    try {
        const translateResult = await AIDeskCoreInstance.chatService.translateText({
            sourceTextList: [sourceText],
            targetLanguage: selectedTranslationLanguage.value,
        });
        const nextTranslatedText = translateResult[0] ?? '';
        translatedText.value = nextTranslatedText;
    } catch (error) {
        console.error(error);
        toast.error(t('翻译失败'));
    }
};

const handleToggleTranslationLanguagePanel = () => {
    showTranslationLanguagePanel.value = !showTranslationLanguagePanel.value;
};

const handleSelectTranslationLanguage = async (languageKey: string) => {
    AIDeskCoreInstance.userInfoService.updateSeatMesssageTranslateTargetLanguage(props.sessionId, languageKey);
    showTranslationLanguagePanel.value = false;
    await handleTranslate();
};

const selectedTranslationLanguageLabel = computed(() => {
    return translationLanguageOptions.value.find(item => item.key === selectedTranslationLanguage.value)?.label ?? t('中文');
});

const handleUseTranslation = async () => {
    if (!translatedText.value) {
        return;
    }

    await applyReEditText(translatedText.value);
    translatedText.value = '';
};

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

const handleUseAIResponse = async () => {
    if (!aiResponseText.value || isAIResponseLoading.value) {
        return;
    }

    await applyReEditText(aiResponseText.value);
    handleCloseAIResponse();
};

const handleCloseAIResponse = () => {
    currentAIResponseReplyId.value = '';
    isAIResponseLoading.value = false;
    aiResponseText.value = '';
};

const handleAIResponse = async () => {
    if (!props.sessionId || props.isInnerSession || isAIResponseLoading.value) {
        return;
    }

    const replyId = uuidv4();
    aiResponseText.value = '';
    currentAIResponseReplyId.value = replyId;
    isAIResponseLoading.value = true;

    try {
        await AIDeskCoreInstance.chatService.AIAssistAnswer({
            sessionId: props.sessionId,
            replyId,
        });
    } catch (error) {
        console.error('AIAssistAnswer request error:', error);
        currentAIResponseReplyId.value = '';
        isAIResponseLoading.value = false;
    }
};

const userDefinedFloatingAction: ChatFloatingAction = {
    key: 'user-defined',
    label: t('自定义'),
    click: async () => {
        await router.push({
            path: `/mobileDesk/chat/${props.sessionId}/userDefined`,
        });
    },
};

const translateFloatingAction: ChatFloatingAction = {
    key: 'translate',
    label: t('翻译'),
    icon: translateIcon,
    click: handleTranslate,
};

const aiResponseFloatingAction: ChatFloatingAction = {
    key: 'ai-response',
    label: t('智能回复'),
    icon: aiResponseIcon,
    loadingIcon: textGeneratingIcon,
    isLoading: () => isAIResponseLoading.value,
    disabled: () => isAIResponseLoading.value,
    click: handleAIResponse,
};

const textPolishFloatingAction: ChatFloatingAction = {
    key: 'text-polish',
    label: t('文本润色'),
    icon: textPolishIcon,
    loadingIcon: textGeneratingIcon,
    isLoading: () => isTextPolishing.value,
    disabled: () => isTextPolishing.value,
    click: handleTextPolish,
};

const floatingActions = computed<ChatFloatingAction[]>(() => {
    const actions: ChatFloatingAction[] = [
        userDefinedFloatingAction,
    ];

    if (msgEnhanceEnable.value === 1) {
        actions.push(textPolishFloatingAction);
    }

    if (seatMsgTranslationEnable.value === 1) {
        actions.push(translateFloatingAction);
    }

    if (!props.isInnerSession && generateReplyMsgEnable.value === 1) {
        actions.push(aiResponseFloatingAction);
    }

    return actions;
});

onMounted(() => {
    AIDeskCoreInstance.eventCenter.addEvent(AI_DESK_EVENTS.AI_ASSIST_ANSWER_EVENT, onAIAssistAnswerEvent);
    AIDeskCoreInstance.coreStore.watch(StoreName.CHAT, {
        quoteMessage: onQuoteMessageUpdated,
    });
    AIDeskCoreInstance.coreStore.watch(StoreName.APP_SETTING, {
        msgEnhanceEnable: onMsgEnhanceEnableUpdated,
        seatMsgTranslationEnable: onSeatMsgTranslationEnableUpdated,
        generateReplyMsgEnable: onGenerateReplyMsgEnableUpdated,
    });
    AIDeskCoreInstance.coreStore.watch(StoreName.USERINFO, {
        seatMesssageTranslateTargetLanguage: onSeatMesssageTranslateTargetLanguage,
    });
});

onUnmounted(() => {
    AIDeskCoreInstance.eventCenter.removeEvent(AI_DESK_EVENTS.AI_ASSIST_ANSWER_EVENT, onAIAssistAnswerEvent);
    AIDeskCoreInstance.coreStore.unwatch(StoreName.CHAT, {
        quoteMessage: onQuoteMessageUpdated,
    });
    AIDeskCoreInstance.coreStore.unwatch(StoreName.APP_SETTING, {
        msgEnhanceEnable: onMsgEnhanceEnableUpdated,
        seatMsgTranslationEnable: onSeatMsgTranslationEnableUpdated,
        generateReplyMsgEnable: onGenerateReplyMsgEnableUpdated,
    });
    AIDeskCoreInstance.coreStore.unwatch(StoreName.USERINFO, {
        seatMesssageTranslateTargetLanguage: onSeatMesssageTranslateTargetLanguage,
    });
});

const onAIAssistAnswerEvent = (data: AIDeskEventParamsMap[AI_DESK_EVENTS.AI_ASSIST_ANSWER_EVENT]) => {
    if (data.sessionId !== props.sessionId) {
        return;
    }

    if (!currentAIResponseReplyId.value || data.replyId !== currentAIResponseReplyId.value) {
        return;
    }

    const mergedText = data.chunks.join('').replace(/\n+/g, '');
    aiResponseText.value = mergedText;

    if (data.isFinished === 1) {
        isAIResponseLoading.value = false;
        currentAIResponseReplyId.value = '';
    }
};

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

const onMsgEnhanceEnableUpdated = (enable: number) => {
    msgEnhanceEnable.value = enable;
};

const onSeatMsgTranslationEnableUpdated = (enable: number) => {
    seatMsgTranslationEnable.value = enable;
};

const onGenerateReplyMsgEnableUpdated = (enable: number) => {
    generateReplyMsgEnable.value = enable;
};

const onSeatMesssageTranslateTargetLanguage = (data: Record<string, string>) => {
    selectedTranslationLanguage.value = getSelectedTranslationLanguage(data);
};

const quoteMessageSender = computed(() => {
    return currentQuoteMessage.value?.messageSenderNick?.trim() || currentQuoteMessage.value?.messageSender || '';
});

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

    return currentQuoteMessage.value.messageAbstract || '';
});

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

const serializeEditorNodes = (nodes: EditorNode[] = []): string => nodes
    .map((node, index) => {
        const content: string = 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') {
        return node.attrs?.alt ?? node.attrs?.title ?? '';
    }

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

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

    draftMessage.value = serializeEditorNode(currentEditor.getJSON() as EditorNode);

    if (draftMessage.value.trim()) {
        showInputToolsPanel.value = false;
    }
};

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) => {
    return {
        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);
    showEmojiPanel.value = false;
    showInputToolsPanel.value = false;
    showVoiceInputMode.value = false;

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

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

    editor.value.chain().focus('end').insertContent(buildInlineContentFromText(text)).run();
    syncDraftMessage(editor.value);
    showEmojiPanel.value = false;
    showInputToolsPanel.value = false;
    showVoiceInputMode.value = false;

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

defineExpose({
    setText: applyReEditText,
});

const handleEditorFocus = () => {
    if (showEmojiPanel.value) {
        showEmojiPanel.value = false;
    }

    if (showInputToolsPanel.value) {
        showInputToolsPanel.value = false;
    }

    if (showVoiceInputMode.value) {
        showVoiceInputMode.value = false;
    }

    if (isVoiceRecordPreparing.value || isVoiceRecording.value) {
        cancelVoiceRecording();
    }
};

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

    showEmojiPanel.value = false;
    showVoiceInputMode.value = false;
    showInputToolsPanel.value = false;
    await nextTick();
    await inputToolsRef.value?.openQuickReply();
};

const editor = useEditor({
    content: '',
    extensions: [
        StarterKit,
        CustomImage.configure({
            inline: true,
            allowBase64: true,
        }),
    ],
    editorProps: {
        attributes: {
            class: 'chat-input-editor',
        },
        handleTextInput: (_view, _from, _to, text) => {
            if (text !== '#') {
                return false;
            }

            void handleQuickReplyTrigger();
            return true;
        },
        handleDOMEvents: {
            focus: () => {
                handleEditorFocus();
                return false;
            },
        },
    },
    onCreate: ({ editor: currentEditor }) => {
        syncDraftMessage(currentEditor);
    },
    onUpdate: ({ editor: currentEditor }) => {
        syncDraftMessage(currentEditor);
    },
});

const hasText = computed(() => Boolean(draftMessage.value.trim()));
const actionButtonIcon = computed(() => {
    if (!showVoiceInputMode.value && hasText.value) {
        return messageSendIcon;
    }

    return showInputToolsPanel.value ? closeInputActionsIcon : openInputActionsIcon;
});
const actionButtonAlt = computed(() => {
    if (!showVoiceInputMode.value && hasText.value) {
        return t('发送消息');
    }

    return showInputToolsPanel.value ? t('关闭输入工具栏') : t('打开输入工具栏');
});
const voiceRecordTipText = computed(() => {
    return isVoiceRecordCancelReady.value ? t('松手取消') : t('松手发送，上移取消');
});
const voiceRecordOverlayBackground = computed(() => {
    return isVoiceRecordCancelReady.value ? sendAudioCancelBackIcon : sendAudioBackIcon;
});
const voiceRecordOverlayIcon = computed(() => {
    return isVoiceRecordCancelReady.value ? sendAudioCancelIcon : sendAudioRecordingIcon;
});

const updateVoiceRecordCancelState = (clientY: number) => {
    const deltaY = voiceRecordPointerStartY.value - clientY;
    isVoiceRecordCancelReady.value = deltaY >= VOICE_RECORD_CANCEL_DISTANCE;
};

const releaseVoiceRecordPointerCapture = () => {
    const trigger = voiceRecordTriggerElement.value;
    const pointerId = voiceRecordPointerId.value;

    if (trigger && pointerId !== null && trigger.hasPointerCapture?.(pointerId)) {
        trigger.releasePointerCapture(pointerId);
    }

    voiceRecordPointerId.value = null;
    voiceRecordTriggerElement.value = null;
};

const handleWindowVoiceRecordPointerMove = (event: PointerEvent) => {
    if (!isVoiceRecordPointerPressed.value) {
        return;
    }

    if (voiceRecordPointerId.value !== null && event.pointerId !== voiceRecordPointerId.value) {
        return;
    }

    event.preventDefault();
    updateVoiceRecordCancelState(event.clientY);
};

const handleWindowVoiceRecordPointerUp = (event: PointerEvent) => {
    if (voiceRecordPointerId.value !== null && event.pointerId !== voiceRecordPointerId.value) {
        return;
    }

    event.preventDefault();
    void handleVoiceRecordPointerUp(event);
};

const handleWindowVoiceRecordPointerCancel = (event: PointerEvent) => {
    if (voiceRecordPointerId.value !== null && event.pointerId !== voiceRecordPointerId.value) {
        return;
    }

    event.preventDefault();
    handleVoiceRecordPointerCancel(event);
};

const bindVoiceRecordWindowEvents = () => {
    if (typeof window === 'undefined' || isVoiceRecordWindowEventsBound) {
        return;
    }

    window.addEventListener('pointermove', handleWindowVoiceRecordPointerMove, { passive: false });
    window.addEventListener('pointerup', handleWindowVoiceRecordPointerUp, { passive: false });
    window.addEventListener('pointercancel', handleWindowVoiceRecordPointerCancel, { passive: false });
    isVoiceRecordWindowEventsBound = true;
};

const unbindVoiceRecordWindowEvents = () => {
    if (typeof window === 'undefined' || !isVoiceRecordWindowEventsBound) {
        return;
    }

    window.removeEventListener('pointermove', handleWindowVoiceRecordPointerMove);
    window.removeEventListener('pointerup', handleWindowVoiceRecordPointerUp);
    window.removeEventListener('pointercancel', handleWindowVoiceRecordPointerCancel);
    isVoiceRecordWindowEventsBound = false;
};

const resetVoiceRecordingState = () => {
    showVoiceRecordOverlay.value = false;
    isVoiceRecordPreparing.value = false;
    isVoiceRecording.value = false;
    isVoiceRecordCancelReady.value = false;
    isVoiceRecordPointerPressed.value = false;
    voiceRecordStartedAt.value = 0;
    voiceRecordPointerStartY.value = 0;
    unbindVoiceRecordWindowEvents();
    releaseVoiceRecordPointerCapture();

    if (voiceRecorder.value) {
        try {
            voiceRecorder.value.stop();
        } catch (error) {
            console.warn(error);
        }
    }

    voiceRecorder.value = null;
};

const createVoiceMessageFile = (audioBlob: Blob, duration: number): VoiceMessageFile => {
    const voiceFile = new File([audioBlob], `voice_${Date.now()}.wav`, {
        type: VOICE_RECORD_FILE_TYPE,
    }) as VoiceMessageFile;

    voiceFile.uri = window.URL.createObjectURL(voiceFile);
    voiceFile.duration = duration;

    return voiceFile;
};

const sendVoiceMessage = async () => {
    const recorder = voiceRecorder.value;
    if (!recorder || !props.sessionId || isSending.value) {
        resetVoiceRecordingState();
        return;
    }

    const duration = Math.max(Date.now() - voiceRecordStartedAt.value, 0);
    if (duration < 1000) {
        try {
            recorder.stop();
        } catch (error) {
            console.warn(error);
        }
        toast.info(t('录音时间太短'));
        resetVoiceRecordingState();
        return;
    }

    let audioBlob: Blob;

    try {
        recorder.stop();
        audioBlob = recorder.getWAVBlob();
    } catch (error) {
        console.error(error);
        toast.error(t('录音失败'));
        resetVoiceRecordingState();
        return;
    }

    const audioFile = createVoiceMessageFile(audioBlob, duration);

    try {
        isSending.value = true;
        await AIDeskCoreInstance.chatService.sendAudioMessage({
            payload: {
                file: audioFile,
            },
        });
    } catch (error) {
        console.error(error);
        toast.error(t('发送语音失败'));
    } finally {
        isSending.value = false;
        resetVoiceRecordingState();
    }
};

const cancelVoiceRecording = () => {
    resetVoiceRecordingState();
};

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

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

const handleToggleVoiceInputMode = async () => {
    const nextVisible = !showVoiceInputMode.value;
    showVoiceInputMode.value = nextVisible;
    showEmojiPanel.value = false;
    showInputToolsPanel.value = false;

    if (!nextVisible || isVoiceRecordPreparing.value || isVoiceRecording.value) {
        cancelVoiceRecording();
    }

    await nextTick();

    if (nextVisible) {
        editor.value?.commands.blur();
        return;
    }

    focusEditor();
};

const handleToggleEmojiPanel = async () => {
    const nextVisible = !showEmojiPanel.value;
    showEmojiPanel.value = nextVisible;
    showInputToolsPanel.value = false;
    await nextTick();

    if (nextVisible) {
        editor.value?.commands.blur();
        return;
    }

    focusEditor();
};

const handleVoiceRecordPointerDown = async (event: PointerEvent) => {
    if (!showVoiceInputMode.value || isSending.value || isVoiceRecordPreparing.value || isVoiceRecording.value) {
        return;
    }

    event.preventDefault();
    const currentSessionId = ++voiceRecordSessionId;
    const trigger = event.currentTarget as HTMLElement | null;
    voiceRecordPointerId.value = event.pointerId;
    voiceRecordTriggerElement.value = trigger;
    trigger?.setPointerCapture?.(event.pointerId);
    bindVoiceRecordWindowEvents();
    isVoiceRecordPointerPressed.value = true;
    isVoiceRecordPreparing.value = true;
    isVoiceRecordCancelReady.value = false;
    showVoiceRecordOverlay.value = true;
    voiceRecordPointerStartY.value = event.clientY;

    const recorder = new Recorder({
        sampleBits: 16,
        sampleRate: 16000,
        numChannels: 1,
    });
    voiceRecorder.value = recorder;

    try {
        await recorder.start();

        if (currentSessionId !== voiceRecordSessionId || !isVoiceRecordPointerPressed.value) {
            try {
                recorder.stop();
            } catch (error) {
                console.warn(error);
            }
            if (voiceRecorder.value === recorder) {
                voiceRecorder.value = null;
            }
            showVoiceRecordOverlay.value = false;
            isVoiceRecordPreparing.value = false;
            return;
        }

        isVoiceRecordPreparing.value = false;
        isVoiceRecording.value = true;
        voiceRecordStartedAt.value = Date.now();
    } catch (error) {
        console.error(error);
        toast.error(t('无法开始录音'));
        resetVoiceRecordingState();
    }
};

const handleVoiceRecordPointerMove = (event: PointerEvent) => {
    if (!isVoiceRecordPointerPressed.value) {
        return;
    }

    if (voiceRecordPointerId.value !== null && event.pointerId !== voiceRecordPointerId.value) {
        return;
    }

    event.preventDefault();
    updateVoiceRecordCancelState(event.clientY);
};

const handleVoiceRecordPointerUp = async (event: PointerEvent) => {
    if (voiceRecordPointerId.value !== null && event.pointerId !== voiceRecordPointerId.value) {
        return;
    }

    event.preventDefault();
    releaseVoiceRecordPointerCapture();

    if (!isVoiceRecordPointerPressed.value) {
        return;
    }

    isVoiceRecordPointerPressed.value = false;

    if (isVoiceRecordPreparing.value) {
        showVoiceRecordOverlay.value = false;
        return;
    }

    if (!isVoiceRecording.value) {
        resetVoiceRecordingState();
        return;
    }

    if (isVoiceRecordCancelReady.value) {
        cancelVoiceRecording();
        return;
    }

    await sendVoiceMessage();
};

const handleVoiceRecordPointerCancel = (event: PointerEvent) => {
    if (voiceRecordPointerId.value !== null && event.pointerId !== voiceRecordPointerId.value) {
        return;
    }

    event.preventDefault();
    releaseVoiceRecordPointerCapture();
    cancelVoiceRecording();
};

const handleActionButtonClick = async () => {
    if (!showVoiceInputMode.value && hasText.value) {
        await handleSend();
        return;
    }

    const nextVisible = !showInputToolsPanel.value;
    showInputToolsPanel.value = nextVisible;
    showEmojiPanel.value = false;
    await nextTick();

    if (nextVisible) {
        editor.value?.commands.blur();
        return;
    }

    focusEditor();
};

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

const handleSelectEmoji = (emojiData: EmojiSelectionData) => {
    if (isTextPolishing.value || !emojiData?.url || !emojiData?.emoji?.key) {
        return;
    }

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

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

    const currentEditor = editor.value;
    if (!currentEditor) {
        return;
    }

    const { selection } = currentEditor.state;

    if (!selection.empty) {
        currentEditor.view.dispatch(currentEditor.state.tr.deleteSelection().scrollIntoView());
        return;
    }

    if (selection.from <= 1) {
        return;
    }

    currentEditor.view.dispatch(currentEditor.state.tr.delete(selection.from - 1, selection.from).scrollIntoView());
};

const handleInputToolSent = () => {
    showInputToolsPanel.value = false;
};

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

    await appendQuickReplyText(text);
};

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

    try {
        isSending.value = true;
        await AIDeskCoreInstance.chatService.sendTextMessage({
            sessionId: props.sessionId,
            payload: {
                text,
            },
        });
        editor.value?.commands.clearContent(true);
        syncDraftMessage(editor.value);
        showEmojiPanel.value = false;
        showInputToolsPanel.value = false;
        editor.value?.commands.blur();
    } catch (error) {
        console.error(error);
        toast.error(t('发送失败'));
    } finally {
        isSending.value = false;
    }
};

onBeforeUnmount(() => {
    cancelVoiceRecording();
    currentTextPolishEnhanceId.value = '';
    currentAIResponseReplyId.value = '';
    isAIResponseLoading.value = false;
    aiResponseText.value = '';
    editor.value?.destroy();
});
</script>

<style scoped>
.chat-input-bar {
    position: relative;
    flex-shrink: 0;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
    background: transparent;
}

.chat-floating-entry-list {
    position: absolute;
    left: 12px;
    bottom: calc(100% + 4px);
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: calc(100% - 70px);
    overflow-x: auto;
    scrollbar-width: none;
}

.chat-floating-entry-list::-webkit-scrollbar {
    display: none;
}

.chat-floating-entry-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    gap: 4px;
    height: 32px;
    padding: 0 14px;
    border: 1px solid #E7E8EA;
    border-radius: 16px;
    background: #fff;
    color: #222;
    font-size: 14px;
    line-height: 20px;
    white-space: nowrap;
}

.chat-floating-entry-button:disabled {
    opacity: 0.7;
}

.chat-floating-entry-button-icon {
    width: 16px;
    height: 16px;
    display: block;
    flex-shrink: 0;
}

.chat-floating-entry-button-icon.is-loading {
    animation: chat-floating-entry-button-spin 1s linear infinite;
}

@keyframes chat-floating-entry-button-spin {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

.chat-translation-language-list {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    overflow-x: auto;
    scrollbar-width: none;
}

.chat-translation-language-list::-webkit-scrollbar {
    display: none;
}

.chat-translation-language-option {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    height: 32px;
    padding: 0 14px;
    border: none;
    border-radius: 16px;
    background: #E6E9F0;
    color: #676A70;
    font-size: 13px;
    line-height: 18px;
    white-space: nowrap;
}

.chat-translation-language-option.is-active {
    background: #E1F7EB;
    color: #09A768;
}

.chat-translation-preview {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.chat-translation-language-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: auto;
    min-width: 62px;
    height: 32px;
    padding: 0 12px;
    border: none;
    border-radius: 16px;
    background: #fff;
    color: #222;
    font-size: 13px;
    line-height: 18px;
    white-space: nowrap;
}

.chat-translation-language-arrow {
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid #222;
}

.chat-translation-content {
    flex: 1;
    min-width: 0;
    min-height: 32px;
    max-height: 72px;
    padding: 7px 14px;
    border-radius: 16px;
    box-sizing: border-box;
    background: #F0F2F7;
    color: #676A70;
    font-size: 13px;
    line-height: 18px;
    overflow-y: auto;
    white-space: pre-wrap;
    word-break: break-word;
    scrollbar-width: none;
}

.chat-translation-content::-webkit-scrollbar {
    display: none;
}

.chat-translation-use-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    height: 32px;
    padding: 0 14px;
    border: none;
    border-radius: 16px;
    background: #222;
    color: #fff;
    font-size: 13px;
    line-height: 18px;
}

.chat-translation-close-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: #fff;
    color: #222;
    font-size: 20px;
    line-height: 20px;
}

.chat-ai-response-preview {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.chat-ai-response-content {
    flex: 1;
    min-width: 0;
    min-height: 32px;
    max-height: 72px;
    padding: 7px 14px;
    border-radius: 16px;
    box-sizing: border-box;
    background: #F0F2F7;
    color: #676A70;
    font-size: 13px;
    line-height: 18px;
    overflow-y: auto;
    white-space: pre-wrap;
    word-break: break-word;
    scrollbar-width: none;
}

.chat-ai-response-content::-webkit-scrollbar {
    display: none;
}

.chat-ai-response-use-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    height: 32px;
    padding: 0 14px;
    border: none;
    border-radius: 16px;
    background: #222;
    color: #fff;
    font-size: 13px;
    line-height: 18px;
}

.chat-ai-response-use-button:disabled {
    background: #F0F2F7;
    color: #fff;
}

.chat-ai-response-close-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: #fff;
    color: #222;
    font-size: 20px;
    line-height: 20px;
}

.chat-input-shell {
    display: flex;
    flex-direction: column;
    min-height: 48px;
    padding: 8px 6px 4px 14px;
    border-radius: 24px;
    background: #fff;
    gap: 6px;
}

.chat-input-quote {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 8px 10px;
    border-radius: 12px;
    background: #F0F2F7;
}

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

.chat-input-quote-sender {
    flex-shrink: 1;
    min-width: 0;
    max-width: 40%;
    color: #676A70;
    font-weight: 500;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.chat-input-quote-abstract {
    flex: 1;
    min-width: 0;
    color: #676A70;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.chat-input-quote-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    background: transparent;
    flex-shrink: 0;
}

.chat-input-quote-close-icon {
    width: 16px;
    height: 16px;
    display: block;
}

.chat-input-main {
    display: flex;
    align-items: flex-end;
    min-width: 0;
}

.chat-input-editor-container {
    position: relative;
    flex: 1;
    min-width: 0;
}

.chat-input-editor-container.is-disabled {
    opacity: 0.6;
}

.chat-input-editor-content {
    min-width: 0;
}

.chat-input-placeholder {
    position: absolute;
    top: 10px;
    left: 0;
    color: #999;
    font-size: 14px;
    line-height: 20px;
    pointer-events: none;
}

.chat-input-actions {
    display: flex;
    align-items: center;
    align-self: center;
    gap: 4px;
    flex-shrink: 0;
    padding-left: 8px;
}

.chat-icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
}

.chat-icon-button:disabled {
    opacity: 0.35;
}

.chat-mode-button {
    margin-right: 8px;
    flex-shrink: 0;
    align-self: center;
}

.chat-voice-trigger {
    flex: 1;
    min-width: 0;
    height: 40px;
    border: none;
    border-radius: 20px;
    background: transparent;
    color: #222;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-user-drag: none;
    touch-action: none;
    overscroll-behavior: contain;
}

.chat-voice-trigger:disabled {
    opacity: 0.4;
}

.chat-voice-record-overlay {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    min-height: calc(88px + env(safe-area-inset-bottom));
    overflow: hidden;
    pointer-events: none;
}

.chat-voice-record-card {
    position: relative;
    flex-shrink: 0;
    width: min(136vw, 980px);
    aspect-ratio: 763 / 550;
    transform: translateY(40%);
}

.chat-voice-record-card-background {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.chat-voice-record-card-content {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    width: 100%;
    height: 100%;
    padding: 164px 24px calc(16px + env(safe-area-inset-bottom));
    box-sizing: border-box;
}

.is-voice-record-overlay-active .chat-input-shell,
.is-voice-record-overlay-active .chat-floating-entry-list,
.is-voice-record-overlay-active :deep(.input-emoji),
.is-voice-record-overlay-active :deep(.input-tools) {
    opacity: 0;
}

.chat-voice-record-tip {
    color: #FFFFFF;
    font-size: 17px;
    line-height: 24px;
    text-align: center;
}

.chat-voice-record-tip.is-cancel {
    color: #E54545;
}

.chat-voice-record-wave {
    width: min(72%, 237px);
    margin-top: 20px;
    display: block;
}

.chat-icon-image {
    width: 24px;
    height: 24px;
    display: block;
}

.chat-action-button:disabled {
    opacity: 0.35;
}

:deep(.chat-input-editor) {
    min-height: 40px;
    max-height: 96px;
    padding: 10px 0;
    color: #222;
    font-size: 14px;
    line-height: 20px;
    word-break: break-word;
    white-space: pre-wrap;
    overflow-y: auto;
    outline: none;
}

:deep(.chat-input-editor[contenteditable='false']) {
    cursor: not-allowed;
}

:deep(.chat-input-editor::-webkit-scrollbar) {
    display: none;
    width: 0;
    height: 0;
}

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

:deep(.chat-input-editor img.custom-image-emoji) {
    width: 20px;
    height: 20px;
    vertical-align: text-bottom;
}
</style>