<!-- =============================================================================
// 企微IT智能服务台 — 回复输入框组件（v5.4 圆角卡片+工具栏+拖拽调整）
// =============================================================================
// 说明：坐席回复消息的输入区域
// 功能：
//   1. 上方拖拽手柄调整输入区高度（textarea 同步伸缩）
//   2. 快捷工具栏（表情/图片/截图/文件/语音/远程协助/快速回复）
//   3. 输入框+发送按钮合为圆角卡片，渐变蓝紫发送按钮
//   4. Enter 发送，Shift+Enter 换行
//   5. 支持粘贴图片/拖拽文件上传（预留接口）
// ============================================================================= -->

<template>
  <div class="reply-box" ref="replyBoxRef">
    <!-- 上方拖拽手柄（调整输入区高度） -->
    <div
      class="input-resize-handle"
      :class="{ dragging: isResizing }"
      @mousedown="startResize"
      title="拖拽调整输入框高度"
    ></div>

    <!-- 引用回复预览（回复某条消息时显示） -->
    <div v-if="replyToMessage" class="reply-preview">
      <div class="reply-preview-bar"></div>
      <div class="reply-preview-content">
        <span class="reply-preview-sender">{{ replyToMessage.sender_name || '未知' }}</span>
        <span class="reply-preview-text">{{ replyToMessage.content?.substring(0, 60) }}{{ replyToMessage.content?.length > 60 ? '...' : '' }}</span>
      </div>
      <button class="reply-preview-close" title="取消回复" @click="$emit('cancelReply')">✕</button>
    </div>

    <!-- 快捷工具栏 -->
    <div class="chat-toolbar">
      <div class="emoji-wrapper">
        <button class="tb-btn" title="表情" @click="showEmojiPicker = !showEmojiPicker">
          😊
          <span class="tb-tip">表情</span>
        </button>
        <!-- 自定义中文表情选择面板（8x8 网格，64个常用表情） -->
        <div v-if="showEmojiPicker" class="emoji-picker-popup">
          <div class="emoji-grid">
            <button
              v-for="emoji in commonEmojis"
              :key="emoji"
              class="emoji-grid__item"
              @click="onEmojiSelect(emoji)"
            >{{ emoji }}</button>
          </div>
        </div>
        <!-- 点击表情面板外部关闭 -->
        <div v-if="showEmojiPicker" class="emoji-picker-overlay" @click="showEmojiPicker = false"></div>
      </div>
      <button class="tb-btn" title="图片" @click="handleToolbarClick('image')">
        🖼
        <span class="tb-tip">图片</span>
      </button>
      <button class="tb-btn" title="截图" @click="handleToolbarClick('screenshot')">
        ✂
        <span class="tb-tip">截图</span>
      </button>
      <button class="tb-btn" title="文件" @click="handleToolbarClick('file')">
        📎
        <span class="tb-tip">文件</span>
      </button>
      <button class="tb-btn" title="语音" @click="handleToolbarClick('voice')">
        🎤
        <span class="tb-tip">语音</span>
      </button>
      <div class="tb-sep"></div>
      <button class="tb-btn" title="邀请员工/部门" @click="showInviteDialog = true">
        👥
        <span class="tb-tip">邀请</span>
      </button>
      <button class="tb-btn" title="远程协助" @click="handleToolbarClick('remote')">
        🖥
        <span class="tb-tip">远程协助</span>
      </button>
      <button class="tb-btn" title="快速回复" @click="handleToolbarClick('quickReply')">
        ⚡
        <span class="tb-tip">快速回复</span>
      </button>
    </div>

    <!-- 输入行（圆角卡片：textarea + 发送按钮） -->
    <div class="chat-input-card">
      <textarea
        ref="inputRef"
        v-model="inputText"
        class="chat-input"
        placeholder="输入回复内容... (Enter发送，Shift+Enter换行)"
        :style="{ height: textareaHeight + 'px' }"
        @keydown="handleKeydown"
        @paste="handlePaste"
      ></textarea>
      <button
        class="btn-send"
        :disabled="!inputText.trim()"
        @click="handleSend"
      >
        发 送
      </button>
    </div>

    <!-- 邀请员工/部门弹窗 -->
    <InviteParticipantDialog
      v-model="showInviteDialog"
      :conversation-id="conversationStore.currentConversation?.id || ''"
      :existing-participant-ids="existingParticipantIds"
      @success="onInviteSuccess"
    />

    <!-- 隐藏的文件输入框（图片/文件上传用，由工具栏按钮触发） -->
    <input
      ref="fileInputRef"
      type="file"
      style="display: none"
      @change="handleFileSelect"
    />

    <!-- 截图区域选择编辑器（对标微信/企微截图体验） -->
    <ScreenshotEditor
      v-if="showScreenshotEditor"
      :screenshot-canvas="screenshotCanvas"
      @confirm="onScreenshotConfirm"
      @cancel="onScreenshotCancel"
    />
  </div>
</template>

<script setup lang="ts">
// ============================================================================
// 导入
// ============================================================================
import { ref, watch, nextTick, onUnmounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import html2canvas from 'html2canvas-pro'
import { useConversationStore } from '@/stores/conversation'
import InviteParticipantDialog from '@/components/conversation/InviteParticipantDialog.vue'
import ScreenshotEditor from './ScreenshotEditor.vue'
import { uploadFile } from '@/api/upload'
import { sendMessage } from '@/api/message'
import type { Message } from '@/api/message'
import { useWebSocket } from '@/composables/useWebSocket'

// ============================================================================
// 工具函数：安全提取错误详情（防止 [object Object]）
// ============================================================================
/**
 * 将 error.response.data.detail 安全转换为字符串
 * FastAPI 422 时 detail 是数组，直接拼接会变成 "[object Object]"
 */
function formatErrorDetail(detail: any): string {
  if (!detail) return ''
  if (typeof detail === 'string') return detail
  if (Array.isArray(detail)) {
    // FastAPI 验证错误：取每个元素的 msg 和 loc
    return detail.map((d: any) => {
      if (d.loc && d.msg) return `${d.loc.slice(1).join('.')}: ${d.msg}`
      if (d.msg) return d.msg
      return JSON.stringify(d)
    }).join('; ')
  }
  if (typeof detail === 'object') return JSON.stringify(detail)
  return String(detail)
}

// ============================================================================
// Props & Emits
// ============================================================================

interface Props {
  /** 引用回复：正在回复的消息（null 表示普通发送） */
  replyToMessage?: Message | null
}

interface Emits {
  /** 发送消息事件 */
  (e: 'send', content: string): void
  /** 取消引用回复 */
  (e: 'cancelReply'): void
}

withDefaults(defineProps<Props>(), {
  replyToMessage: null,
})

const emit = defineEmits<Emits>()

// ============================================================================
// 状态
// ============================================================================

/** 输入框文本 */
const inputText = ref('')

/** 输入框 DOM 引用 */
const inputRef = ref<HTMLTextAreaElement | null>(null)

/** 隐藏文件输入框 DOM 引用（用于触发系统文件选择器） */
const fileInputRef = ref<HTMLInputElement | null>(null)

/** ReplyBox 容器 DOM 引用 */
const replyBoxRef = ref<HTMLElement | null>(null)

/** 会话 Store */
const conversationStore = useConversationStore()

/** WebSocket 组合函数（用于发送 typing 事件） */
const { sendTyping } = useWebSocket()

/** textarea 高度（px），默认3行约60px */
const textareaHeight = ref(60)

/** 是否正在拖拽调整高度 */
const isResizing = ref(false)

/** 邀请弹窗是否可见 */
const showInviteDialog = ref(false)

/** 截图编辑器是否可见 */
const showScreenshotEditor = ref(false)

/** html2canvas 生成的完整页面截图 Canvas 对象（传给 ScreenshotEditor） */
let screenshotCanvas: HTMLCanvasElement | null = null
const showEmojiPicker = ref(false)

/**
 * 常用表情列表（8行x8列 = 64 个常用表情）
 * 覆盖日常沟通场景，与 H5 端保持一致
 */
const commonEmojis = [
  '😀','😃','😄','😁','😆','😅','🤣','😂',
  '🙂','😊','😇','🥰','😍','🤩','😘','😗',
  '😚','😙','😋','😛','😜','🤪','😝','🤑',
  '🤗','🤭','🤫','🤔','🤐','🤨','😐','😑',
  '😶','😏','😒','🙄','😬','😮','🤯','😲',
  '😳','🥺','😢','😭','😤','😠','😡','🤬',
  '👍','👎','👏','🙌','🤝','💪','✌️','🤞',
  '❤️','🧡','💛','💚','💙','💜','💯','✅',
]

/** 已在参与者列表中的ID（排除已有员工和参与者） */
const existingParticipantIds = computed(() => {
  const conv = conversationStore.currentConversation
  if (!conv) return []
  const ids: string[] = [conv.employee_id] // 排除发起咨询的员工自己
  if (conv.participants) {
    ids.push(...conv.participants.map((p: any) => p.id))
  }
  return ids
})

/** 邀请成功回调 */
function onInviteSuccess(): void {
  // 刷新会话列表（重新拉取最新数据）
  conversationStore.fetchConversations()
}

/** 表情选择回调 — 将选中的表情插入到输入框 */
function onEmojiSelect(emoji: string): void {
  inputText.value += emoji
  showEmojiPicker.value = false
  nextTick(() => {
    inputRef.value?.focus()
  })
}

/** 拖拽相关临时变量 */
let resizeStartY = 0
let resizeStartHeight = 0
let resizeStartTextareaHeight = 0

// ============================================================================
// 监听
// ============================================================================

/**
 * 监听 pendingReplyText 变化
 * 当快速回复模板设置待填充文本时，自动填充到输入框
 */
watch(
  () => conversationStore.pendingReplyText,
  (newVal) => {
    if (newVal) {
      inputText.value = newVal
      conversationStore.pendingReplyText = ''
      nextTick(() => {
        inputRef.value?.focus()
      })
    }
  }
)

/**
 * 监听输入框文本变化，发送 typing 事件
 * 当用户输入文字时，通知 WebSocket 让其他人看到"正在输入..."
 * 节流：3 秒内最多发送一次（在 sendTyping 内部控制）
 */
watch(inputText, () => {
  const convId = conversationStore.currentConversation?.id
  if (convId && inputText.value.trim()) {
    sendTyping(convId)
  }
})

// ============================================================================
// 拖拽调整输入区高度
// ============================================================================

/** 开始拖拽 */
function startResize(e: MouseEvent): void {
  e.preventDefault()
  isResizing.value = true
  resizeStartY = e.clientY
  resizeStartHeight = replyBoxRef.value?.offsetHeight || 80
  resizeStartTextareaHeight = textareaHeight.value
  document.addEventListener('mousemove', onResizeMove)
  document.addEventListener('mouseup', onResizeEnd)
  document.body.style.cursor = 'row-resize'
  document.body.style.userSelect = 'none'
}

/** 拖拽中 */
function onResizeMove(e: MouseEvent): void {
  if (!isResizing.value) return
  // 向上拖 → 输入区变高（delta为负），向下拖 → 变矮
  const dy = resizeStartY - e.clientY
  const newBoxH = Math.max(80, Math.min(400, resizeStartHeight + dy))
  const delta = newBoxH - resizeStartHeight
  const newTaH = Math.max(60, Math.min(300, resizeStartTextareaHeight + delta))
  textareaHeight.value = newTaH
}

/** 结束拖拽 */
function onResizeEnd(): void {
  isResizing.value = false
  document.removeEventListener('mousemove', onResizeMove)
  document.removeEventListener('mouseup', onResizeEnd)
  document.body.style.cursor = ''
  document.body.style.userSelect = ''
}

// ============================================================================
// 方法
// ============================================================================

/**
 * 处理键盘事件
 * Enter 发送消息，Shift+Enter 换行
 */
function handleKeydown(event: KeyboardEvent): void {
  // Enter 且没有按 Shift → 发送
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault()
    handleSend()
  }
  // Shift+Enter → 默认换行，不处理
}

/**
 * 发送消息
 */
async function handleSend(): Promise<void> {
  const content = inputText.value.trim()
  if (!content) return

  try {
    emit('send', content)
    inputText.value = ''
    // 恢复 textarea 高度（3行默认）
    textareaHeight.value = 60
  } catch (error) {
    console.error('发送消息失败:', error)
  }
}

/**
 * 处理粘贴事件
 * 做什么：检测剪贴板中的图片或文件，自动上传并发送消息
 * 为什么：用户需要从剪贴板直接粘贴图片/文件，不用每次点按钮
 *
 * 支持：
 *   1. 粘贴图片（截图工具 Ctrl+V / 复制图片）
 *   2. 粘贴文件（从文件管理器复制的文件）
 *   3. 纯文本粘贴（默认行为，不拦截）
 */
async function handlePaste(event: ClipboardEvent): Promise<void> {
  const items = event.clipboardData?.items
  if (!items) return

  for (const item of Array.from(items)) {
    // 情况1：剪贴板包含文件（图片/任意文件）
    if (item.kind === 'file') {
      event.preventDefault()           // 阻止默认粘贴（文件不插入文本）
      const file = item.getAsFile()
      if (!file) continue

      // 根据文件类型选择上传方式
      if (file.type.startsWith('image/')) {
        await handleImageUpload(file)
      } else {
        await handleFileUpload(file)
      }
      return                       // 每次粘贴只处理第一个文件
    }

    // 情况2：剪贴板包含图片类型数据（如截图工具复制的PNG）
    if (item.type.startsWith('image/') && item.kind === 'string') {
      // 某些浏览器将截图放在 item.getAsFile() 中，上面 'file' 分支已覆盖
      // 这里仅作防御性检查
      event.preventDefault()
      const file = item.getAsFile()
      if (file) {
        await handleImageUpload(file)
        return
      }
    }
  }
  // 纯文本：不拦截，浏览器默认行为（插入文本到输入框）
}

/**
 * 上传图片并发送图片消息
 *
 * @param file - 图片文件对象（File 或 Blob）
 */
async function handleImageUpload(file: File | Blob): Promise<void> {
  const convId = conversationStore.currentConversation?.id
  if (!convId) {
    ElMessage.warning('请先选择一个会话')
    return
  }

  try {
    ElMessage.info('图片上传中...')

    // 1. 上传图片到服务器
    const result = await uploadFile(file)

    // 2. 发送图片消息
    const newMsg = await sendMessage(convId, '[图片]', 'image', {
      media_url: result.url,
      file_name: result.filename,
      file_size: result.file_size,
    })

    // 3. 把新消息加入 store，让界面立即刷新显示
    conversationStore.messages.push(newMsg)

    ElMessage.success('图片发送成功')
  } catch (error: any) {
    console.error('图片上传失败:', error)
    ElMessage.error(
      formatErrorDetail(error?.response?.data?.detail) ||
      error?.message ||
      '图片上传失败，请重试'
    )
  }
}

/**
 * 上传文件并发送文件消息
 * 做什么：处理非图片文件的上传和发送
 * 为什么：粘贴功能需要支持任意文件类型，不仅限于图片
 *
 * @param file - 文件对象（File 或 Blob）
 */
async function handleFileUpload(file: File | Blob): Promise<void> {
  const convId = conversationStore.currentConversation?.id
  if (!convId) {
    ElMessage.warning('请先选择一个会话')
    return
  }

  try {
    const fileName = file instanceof File ? file.name : '文件'
    ElMessage.info(`正在上传: ${fileName}`)

    // 1. 上传文件到服务器
    const result = await uploadFile(file)

    // 2. 发送文件消息
    const newMsg = await sendMessage(convId, `[文件] ${result.filename}`, 'file', {
      media_url: result.url,
      file_name: result.filename,
      file_size: result.file_size,
    })

    // 3. 把新消息加入 store，让界面立即刷新显示
    conversationStore.messages.push(newMsg)

    ElMessage.success('文件发送成功')
  } catch (error: any) {
    console.error('文件发送失败:', error)
    ElMessage.error(
      formatErrorDetail(error?.response?.data?.detail) ||
      error?.message ||
      '文件发送失败，请重试'
    )
  }
}

/**
 * 处理文件选择上传
 * 通过隐藏的 <input type="file"> 触发
 */
async function handleFileSelect(event: Event): Promise<void> {
  const input = event.target as HTMLInputElement
  const files = input.files
  if (!files || files.length === 0) return

  const convId = conversationStore.currentConversation?.id
  if (!convId) {
    ElMessage.warning('请先选择一个会话')
    return
  }

  for (const file of Array.from(files)) {
    try {
      // 显示上传中提示（仅第一个文件显示）
      if (file === files[0]) {
        ElMessage.info(`正在上传: ${file.name}`)
      }

      // 1. 上传文件到服务器
      const result = await uploadFile(file)

      // 2. 发送文件消息
      const isImage = result.msg_type === 'image'
      const newMsg = await sendMessage(
        convId,
        isImage ? '[图片]' : `[文件] ${file.name}`,
        isImage ? 'image' : 'file',
        {
          media_url: result.url,
          file_name: result.filename,
          file_size: result.file_size,
        }
      )

      // 3. 把新消息加入 store，让界面立即刷新显示
      conversationStore.messages.push(newMsg)

      ElMessage.success(`${file.name} 发送成功`)
    } catch (error: any) {
      console.error('文件上传失败:', error)
      ElMessage.error(
        `${file.name} 上传失败: ${
          formatErrorDetail(error?.response?.data?.detail) ||
          error?.message ||
          '请重试'
        }`
      )
    }
  }

  // 重置 input，允许重复选择同一文件
  input.value = ''
}

/**
 * 快捷工具栏按钮点击处理
 * 各功能对应的实际行为
 */
function handleToolbarClick(action: string): void {
  switch (action) {
    case 'image':
      // 图片上传：触发文件选择器（限定图片类型）
      if (fileInputRef.value) {
        fileInputRef.value.accept = 'image/*'
        fileInputRef.value.multiple = true
        fileInputRef.value.click()
      }
      break

    case 'file':
      // 文件上传：触发文件选择器（不限类型）
      if (fileInputRef.value) {
        fileInputRef.value.accept = ''
        fileInputRef.value.multiple = true
        fileInputRef.value.click()
      }
      break

    case 'quickReply':
      // 快速回复按钮：聚焦输入框（快速回复面板已在右栏可用）
      inputRef.value?.focus()
      break

    case 'screenshot':
      // 截图功能：截取页面后进入区域选择模式（对标微信/企微）
      handleScreenshot()
      break

    default:
      // 其他功能暂未实现
      const actionMap: Record<string, string> = {
        voice: '语音消息功能开发中',
        remote: '远程协助功能开发中',
      }
      ElMessage.info(actionMap[action] || '功能开发中')
  }
}

/**
 * 处理截图功能
 *
 * 做什么：使用 html2canvas 截取当前页面内容，弹出预览，确认后上传发送
 *
 * 为什么用 html2canvas：
 *   - Screen Capture API（getDisplayMedia）在企微桌面端被限制，不稳定
 *   - html2canvas 纯前端渲染，不依赖任何浏览器 API，所有环境都可用
 *   - 适合客服场景：坐席想截取当前对话内容发给用户
 *
 * 交互流程：点击截图按钮 → 截取页面 → 弹出预览对话框 → 确认发送 / 取消
 */
async function handleScreenshot(): Promise<void> {
  const convId = conversationStore.currentConversation?.id
  if (!convId) {
    ElMessage.warning('请先选择一个会话')
    return
  }

  try {
    ElMessage.info('正在截取页面...')

    // 1. 截取整个页面
    const canvas = await html2canvas(document.body, {
      useCORS: true,
      scale: window.devicePixelRatio || 1,
      logging: false,
      backgroundColor: '#ffffff',
    })

    // 2. 保存 canvas 并显示截图编辑器（对标微信/企微）
    screenshotCanvas = canvas
    showScreenshotEditor.value = true
  } catch (error) {
    console.error('截图失败:', error)
    ElMessage.error('截图失败，请重试')
  }
}

/**
 * 确认发送截图
 * 上传截图并发送图片消息
 */
async function onScreenshotConfirm(blob: Blob): Promise<void> {
  const convId = conversationStore.currentConversation?.id
  if (!convId) return

  try {
    ElMessage.info('截图上传中...')

    const result = await uploadFile(blob)

    const newMsg = await sendMessage(convId, '[截图]', 'image', {
      media_url: result.url,
      file_name: result.filename,
      file_size: result.file_size,
    })

    // 把新消息加入 store，让界面立即刷新显示
    conversationStore.messages.push(newMsg)

    ElMessage.success('截图发送成功')
  } catch (error: any) {
    console.error('[ReplyBox] 截图发送失败:', error)
    const errMsg =
      formatErrorDetail(error?.response?.data?.detail) ||
      error?.message ||
      '未知错误'
    ElMessage.error(`截图发送失败：${errMsg}`)
  } finally {
    showScreenshotEditor.value = false
    screenshotCanvas = null
  }
}

/**
 * 截图编辑器取消回调
 */
function onScreenshotCancel(): void {
  showScreenshotEditor.value = false
  screenshotCanvas = null
}

// 组件卸载时清理拖拽事件
onUnmounted(() => {
  document.removeEventListener('mousemove', onResizeMove)
  document.removeEventListener('mouseup', onResizeEnd)
})
</script>

<style scoped>
/* 整体容器 */
.reply-box {
  padding: 0 12px 8px 12px;
  background: var(--bg-primary);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* 上方拖拽手柄 */
.input-resize-handle {
  height: 4px;
  cursor: row-resize;
  background: var(--border);
  transition: background 0.2s;
  margin: 0 -12px 0 -12px;
  flex-shrink: 0;
}
.input-resize-handle:hover,
.input-resize-handle.dragging {
  background: var(--accent);
}

/* 快捷工具栏 */
.chat-toolbar {
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 8px 6px 4px 6px;
  flex-shrink: 0;
}

.tb-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 28px;
  border: none;
  background: transparent;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 14px;
  color: var(--text-muted);
  transition: all 0.2s;
  position: relative;
}
.tb-btn:hover {
  background: var(--accent-soft);
  color: var(--accent);
}
.tb-btn:active {
  transform: scale(0.92);
}

/* 工具提示气泡 */
.tb-tip {
  display: none;
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 6px;
  background: var(--text-primary);
  color: var(--bg-secondary);
  font-size: 10px;
  padding: 3px 8px;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: var(--shadow);
}
.tb-tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: var(--text-primary);
}
.tb-btn:hover .tb-tip {
  display: block;
}

/* 工具栏分隔线 */
.tb-sep {
  width: 1px;
  height: 14px;
  background: var(--border);
  margin: 0 6px;
  flex-shrink: 0;
}

/* 输入卡片（textarea + 发送按钮，统一圆角卡片） */
.chat-input-card {
  display: flex;
  align-items: flex-end;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color 0.25s, box-shadow 0.25s;
  box-shadow: var(--shadow-sm);
}
.chat-input-card:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* 输入框 — 默认3行（60px），最大300px，自适应内容高度 */
.chat-input {
  flex: 1;
  background: transparent;
  border: none;
  padding: 10px 0 10px 14px;
  color: var(--text-primary);
  font-size: 13px;
  font-family: inherit;
  resize: none;
  min-height: 60px;
  max-height: 300px;
  outline: none;
  line-height: 1.5;
  overflow-y: auto;
}
.chat-input:focus {
  outline: none;
}
.chat-input::placeholder {
  color: var(--text-muted);
}

/* 发送按钮 — 内嵌于卡片右侧，渐变蓝紫 */
.btn-send {
  padding: 8px 18px;
  margin: 4px 6px 4px 0;
  background: linear-gradient(135deg, var(--accent), var(--purple));
  color: #fff;
  border: none;
  border-radius: var(--radius);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.25s;
  flex-shrink: 0;
  letter-spacing: 0.5px;
}
.btn-send:hover:not(:disabled) {
  opacity: 0.9;
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.btn-send:active:not(:disabled) {
  transform: scale(0.95);
}
.btn-send:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* 表情选择器弹出层（自定义中文表情网格） */
.emoji-wrapper {
  position: relative;
}

.emoji-picker-popup {
  position: absolute;
  bottom: 36px;
  left: 0;
  z-index: 100;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
  max-height: 240px;
  overflow-y: auto;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}

/* 8x8 表情网格 */
.emoji-grid {
  display: grid;
  grid-template-columns: repeat(8, 32px);
  gap: 2px;
}

.emoji-grid__item {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  border: none;
  background: transparent;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
  outline: none;
  padding: 0;
}

.emoji-grid__item:hover {
  background: var(--accent-soft);
}

.emoji-grid__item:active {
  transform: scale(1.2);
}

/* 表情面板遮罩（点击外部关闭） */
.emoji-picker-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 99;
}

/* ============================================================================
// 引用回复预览样式
// ============================================================================ */

.reply-preview {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: var(--bg-tertiary);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.reply-preview-bar {
  width: 3px;
  height: 28px;
  border-radius: 2px;
  background: var(--accent);
  flex-shrink: 0;
}

.reply-preview-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.reply-preview-sender {
  font-size: 12px;
  color: var(--accent);
  font-weight: 500;
}

.reply-preview-text {
  font-size: 12px;
  color: var(--text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reply-preview-close {
  width: 20px;
  height: 20px;
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: 4px;
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: all 0.2s;
}

.reply-preview-close:hover {
  background: var(--bg-secondary);
  color: var(--text-primary);
}

</style>
