<script setup lang="ts">
import { computed, nextTick, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Close, Picture } from '@element-plus/icons-vue'
import {
  fetchAdminChat,
  fetchAdminChatMessages,
  fetchAdminChats,
  fetchQuickReplies,
  markAdminChatRead,
  sendAdminChatMessage,
  updateChatRemark,
  type AdminChatCounts,
  type ChatConversation,
  type ChatMessage,
  type QuickReply,
} from '@/features/chats'
import {
  fetchAdminLatestOrderByListing,
  fetchAdminHandoffRecords,
  fetchAdminOrder,
  type HandoffRecord,
  type Order,
} from '@/features/orders'
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
import { uploadAdminFile } from '@/shared/api/files'
import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue'
import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE'
import { useDesktopNotification } from '@/features/chats/composables/useDesktopNotification'
import NotificationSettings from '@/features/chats/components/NotificationSettings.vue'
import { adminPath } from '@/shared/utils/adminPath'
import { centToYuan, formatCentWithSymbol, formatMoney } from '@/shared/utils/money'
import { formatDateMinute, formatDateTime } from '@/shared/utils/time'
import { formatListingNo } from '@/shared/utils/listingDisplay'
import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
import TransferDialog from '../components/TransferDialog.vue'
import QuickReplyDialog from '../components/QuickReplyDialog.vue'

const currentAdminId = Number(localStorage.getItem('admin_id') || 0)
const route = useRoute()

const conversations = ref<ChatConversation[]>([])
const active = ref<ChatConversation | null>(null)
const messages = ref<ChatMessage[]>([])
const orderLoading = ref(false)
const activeOrder = ref<Order | null>(null)
const activeHandoffRecords = ref<HandoffRecord[]>([])
const activePaymentRecords = ref<AdminPayment[]>([])
const loading = ref(false)
const messageLoading = ref(false)
const sending = ref(false)
const uploading = ref(false)
const content = ref('')
const attachments = ref<string[]>([])
const listRef = ref<HTMLElement | null>(null)
const fileInputRef = ref<HTMLInputElement | null>(null)
const filter = ref<'all' | 'mine' | 'unassigned'>('mine')
const stage = ref<'all' | 'pending' | 'unjoined' | 'handoff' | 'renting' | 'after_sale' | 'ended'>(
  'pending'
)
const keyword = ref('')
const chatCounts = ref<AdminChatCounts>({})
const transferVisible = ref(false)
const quickReplyVisible = ref(false)
const quickReplies = ref<QuickReply[]>([])
const remarkEditing = ref(false)
const remarkValue = ref('')
let searchTimer: ReturnType<typeof setTimeout> | null = null

const ownershipTabs = [
  { key: 'mine', label: '我的' },
  { key: 'all', label: '全部' },
  { key: 'unassigned', label: '未分配' },
] as const
const stageTabs = [
  { key: 'pending', label: '待处理' },
  { key: 'unjoined', label: '无订单' },
  { key: 'handoff', label: '待交接' },
  { key: 'renting', label: '使用中' },
  { key: 'after_sale', label: '售后中' },
  { key: 'ended', label: '已结束' },
  { key: 'all', label: '全部阶段' },
] as const

const activeMembers = computed(() => {
  const participants = active.value?.participants || []
  return participants
    .map(item => {
      const remark = getParticipantRemark()
      const name = remark ? `${remark}(${item.display_name})` : item.display_name
      return `${roleLabel(item.role)}：${name}`
    })
    .join(' / ')
})
const canSend = computed(() => content.value.trim() !== '' || attachments.value.length > 0)
const activeListingCode = computed(() =>
  activeOrder.value
    ? formatListingNo(activeOrder.value.listing_no, activeOrder.value.listing_id)
    : '-'
)
const latestHandoffRecord = computed(() => activeHandoffRecords.value[0])
const latestPaymentRecord = computed(() => activePaymentRecords.value[0])
const hasOrderContext = computed(() => Boolean(active.value?.order_id || active.value?.listing_id))
const orderDetailLink = computed(() =>
  activeOrder.value
    ? {
        path: adminPath(`orders/${activeOrder.value.id}`),
        query: {
          from: 'chat',
          chat_id: String(active.value?.id || ''),
          chat_filter: filter.value,
          chat_stage: stage.value,
          ...(keyword.value.trim() ? { chat_keyword: keyword.value.trim() } : {}),
        },
      }
    : ''
)
let orderLoadToken = 0

// 桌面通知
const desktopNotification = useDesktopNotification('admin')

function getParticipantRemark() {
  if (!active.value) return ''
  const myParticipant = active.value.participants?.find(
    p => p.participant_type === 'admin' && p.participant_id === currentAdminId
  )
  return myParticipant?.remark || ''
}

function handleSSEEvent(event: ChatEvent) {
  if (event.type === 'conversation_updated') {
    loadConversations(false)
  }
  if (event.type === 'conversation_read') {
    // 对方已读：仅当本端仍有未获回执的自己消息时重载，刷新「已读」状态
    if (active.value && event.conversation_id === active.value.id) {
      const isSelfReader = event.reader_type === 'admin' && event.reader_id === currentAdminId
      if (!isSelfReader && messages.value.some(m => m.is_self && !m.is_read)) {
        loadMessages(active.value.id, false)
      }
    }
  }
  if (event.type === 'new_message') {
    const msg = event.message
    if (!msg) return
    const isSelf = msg.sender_type === 'admin' && msg.sender_id === currentAdminId
    if (
      active.value &&
      event.conversation_id === active.value.id &&
      !messages.value.some(m => m.id === msg.id)
    ) {
      messages.value = [
        ...messages.value,
        {
          id: msg.id,
          conversation_id: msg.conversation_id,
          sender_type: msg.sender_type as ChatMessage['sender_type'],
          sender_id: msg.sender_id,
          sender_role: msg.sender_role as ChatMessage['sender_role'],
          sender_name: msg.sender_name,
          sender_avatar: '',
          is_self: isSelf,
          is_read: false,
          content_type: msg.content_type as ChatMessage['content_type'],
          content: msg.content,
          attachment_urls: msg.attachment_urls || [],
          created_at: msg.created_at,
        },
      ]
      nextTick(() => scrollBottom())
    }

    loadConversations(false)

    // 不是自己发送的消息才发送通知；当前正在看的会话且页面前台时会在 notify 内部静默。
    if (!isSelf) {
      desktopNotification.notify(event, active.value?.id ?? null)
    }
  }
}

const { onEvent } = useChatSSE('admin', '/api/admin/chats/events')
onEvent(handleSSEEvent)

onMounted(async () => {
  // 静默请求通知权限（用户交互后才会弹窗）
  desktopNotification.requestPermissionSilently()
  const routeFilter = firstQueryValue(route.query.chat_filter)
  if (['all', 'mine', 'unassigned'].includes(routeFilter)) {
    filter.value = routeFilter as typeof filter.value
  }
  const routeStage = firstQueryValue(route.query.chat_stage)
  if (stageTabs.some(item => item.key === routeStage)) {
    stage.value = routeStage as typeof stage.value
  }
  keyword.value = firstQueryValue(route.query.chat_keyword)
  await Promise.all([loadConversations(), loadQuickReplies()])
})

async function loadConversations(showLoading = true) {
  if (showLoading) loading.value = true
  try {
    const res = await fetchAdminChats(1, 100, {
      filter: filter.value,
      stage: stage.value,
      keyword: keyword.value.trim(),
    })
    conversations.value = res.items
    chatCounts.value = res.counts || {}
    const first = conversations.value[0]
    if (!active.value) {
      const routeChatID = Number(firstQueryValue(route.query.chat_id) || 0)
      if (routeChatID) {
        await openConversationById(routeChatID, false)
      } else if (first) {
        await openConversationById(first.id, false)
      }
    }
  } catch {
    ElMessage.error('会话加载失败')
  } finally {
    loading.value = false
  }
}

async function loadQuickReplies() {
  try {
    quickReplies.value = await fetchQuickReplies()
  } catch {
    /* ignore */
  }
}

async function openConversation(item: ChatConversation) {
  await openConversationById(item.id)
}

async function openConversationById(id: number, refreshList = true) {
  messageLoading.value = true
  try {
    const chat = await fetchAdminChat(id)
    active.value = chat
    void loadOrderContext(chat)
    await loadMessages(id)
    await markAdminChatRead(id)
    if (refreshList) await loadConversations(false)
    remarkEditing.value = false
    remarkValue.value = ''
    attachments.value = []
  } catch {
    ElMessage.error('会话详情加载失败')
  } finally {
    messageLoading.value = false
  }
}

async function loadOrderContext(chat: ChatConversation) {
  const token = ++orderLoadToken
  activeOrder.value = null
  activeHandoffRecords.value = []
  activePaymentRecords.value = []
  if (!chat.order_id && !chat.listing_id) {
    orderLoading.value = false
    return
  }
  orderLoading.value = true
  try {
    const resolvedOrder = chat.order_id
      ? await fetchAdminOrder(chat.order_id)
      : await fetchAdminLatestOrderByListing(chat.listing_id as number)
    if (token !== orderLoadToken) return
    const handoffRecords = await fetchAdminHandoffRecords(resolvedOrder.id)
    if (token !== orderLoadToken) return
    activeOrder.value = resolvedOrder
    activeHandoffRecords.value = handoffRecords
    fetchAdminPayments({ order_id: String(resolvedOrder.id), page_size: 5, silent: true })
      .then(paymentRecords => {
        if (token === orderLoadToken) activePaymentRecords.value = paymentRecords.items
      })
      .catch(() => {
        if (token === orderLoadToken) activePaymentRecords.value = []
      })
  } catch {
    if (token === orderLoadToken) {
      activeOrder.value = null
      activeHandoffRecords.value = []
      activePaymentRecords.value = []
    }
  } finally {
    if (token === orderLoadToken) orderLoading.value = false
  }
}

async function loadMessages(id: number, scroll = true) {
  const res = await fetchAdminChatMessages(id, 1, 100)
  messages.value = res.items
  if (scroll) {
    await nextTick()
    scrollBottom()
  }
}

async function handleSend() {
  const text = content.value.trim()
  const imageUrls = [...attachments.value]
  if (!active.value || (!text && imageUrls.length === 0) || sending.value || uploading.value) return
  sending.value = true
  try {
    const sent = await sendAdminChatMessage(active.value.id, text, imageUrls)
    appendMessage(sent)
    content.value = ''
    attachments.value = []
    await loadConversations(false)
  } catch {
    ElMessage.error('发送失败')
  } finally {
    sending.value = false
  }
}

function appendMessage(message: ChatMessage) {
  if (messages.value.some(item => item.id === message.id)) return
  messages.value = [...messages.value, message]
  nextTick(() => scrollBottom())
}

function pickImages() {
  if (uploading.value || attachments.value.length >= 9) return
  fileInputRef.value?.click()
}

async function handleImageChange(event: Event) {
  const input = event.target as HTMLInputElement
  const files = Array.from(input.files || [])
  input.value = ''
  if (files.length === 0) return
  await uploadImages(files)
}

async function handlePaste(event: ClipboardEvent) {
  const items = Array.from(event.clipboardData?.items || [])
  const imageFiles = items
    .filter(item => item.type.startsWith('image/'))
    .map(item => item.getAsFile())
    .filter(Boolean) as File[]

  if (imageFiles.length > 0) {
    event.preventDefault()
    await uploadImages(imageFiles)
  }
}

async function uploadImages(files: File[]) {
  const slots = 9 - attachments.value.length
  if (slots <= 0) {
    ElMessage.warning('每条消息最多发送 9 张图片')
    return
  }
  uploading.value = true
  try {
    for (const file of files.slice(0, slots)) {
      if (
        !['image/jpeg', 'image/png', 'image/webp'].includes(file.type) ||
        file.size > 25 * 1024 * 1024
      ) {
        ElMessage.warning(`${file.name} 不符合图片规则`)
        continue
      }
      const uploaded = await uploadAdminFile(file, 'chat')
      attachments.value.push(uploaded.url)
    }
    if (files.length > slots) {
      ElMessage.warning('每条消息最多发送 9 张图片')
    }
  } catch {
    ElMessage.error('图片上传失败')
  } finally {
    uploading.value = false
  }
}

function removeAttachment(index: number) {
  attachments.value.splice(index, 1)
}

function handleQuickReplySelect(reply: QuickReply) {
  content.value = reply.content
  quickReplyVisible.value = false
}

function handleFilterChange(val: string) {
  filter.value = val as typeof filter.value
  resetActiveConversation()
  loadConversations()
}

function handleStageChange(val: string) {
  stage.value = val as typeof stage.value
  resetActiveConversation()
  loadConversations()
}

function handleKeywordInput() {
  if (searchTimer) clearTimeout(searchTimer)
  searchTimer = setTimeout(() => {
    resetActiveConversation()
    loadConversations()
  }, 320)
}

function handleKeywordSearch() {
  if (searchTimer) clearTimeout(searchTimer)
  resetActiveConversation()
  loadConversations()
}

function resetActiveConversation() {
  active.value = null
  messages.value = []
  activeOrder.value = null
  activeHandoffRecords.value = []
  activePaymentRecords.value = []
}

function handleTransferSuccess() {
  loadConversations(false)
  if (active.value) {
    loadMessages(active.value.id, false)
  }
}

async function startEditRemark() {
  if (!active.value) return
  const myParticipant = active.value.participants?.find(
    p => p.participant_type === 'admin' && p.participant_id === currentAdminId
  )
  remarkValue.value = myParticipant?.remark || active.value.title
  remarkEditing.value = true
}

async function saveRemark() {
  if (!active.value) return
  try {
    await updateChatRemark(active.value.id, remarkValue.value)
    ElMessage.success('备注已更新')
    remarkEditing.value = false
    await fetchAdminChat(active.value.id).then(chat => {
      active.value = chat
    })
    await loadConversations(false)
  } catch {
    ElMessage.error('更新备注失败')
  }
}

function scrollBottom() {
  const el = listRef.value
  if (!el) return
  el.scrollTop = el.scrollHeight
}

function roleLabel(role: string) {
  const map: Record<string, string> = {
    renter: '租客',
    owner: '号主',
    support: '客服',
    customer: '咨询',
    system: '系统',
    admin: '管理员',
  }
  return map[role] || '成员'
}

function senderLabel(item: ChatMessage) {
  if (item.sender_type === 'system') return '系统'
  // 管理员消息特殊标识
  if (item.sender_role === 'admin') {
    return `管理员 · ${item.sender_name}`
  }
  return `${roleLabel(item.sender_role)} · ${item.sender_name}`
}

function getConversationTitle(item: ChatConversation) {
  const myParticipant = item.participants?.find(
    p => p.participant_type === 'admin' && p.participant_id === currentAdminId
  )
  return myParticipant?.remark || item.title
}

function getSupportName(item: ChatConversation) {
  const support = item.participants?.find(p => p.role === 'support')
  return support?.display_name || '未分配'
}

function ownershipCount(key: string) {
  return Number(chatCounts.value.ownership?.[key] || 0)
}

function stageCount(key: string) {
  return Number(chatCounts.value.stages?.[key] || 0)
}

function conversationStageLabel(item: ChatConversation) {
  if (item.unread_count > 0 || item.last_sender_type === 'user') return '待处理'
  if (!item.latest_order_id) return '无订单'
  if (item.latest_order_status === 'pending_handoff') return '待交接'
  if (['renting', 'overdue'].includes(item.latest_order_status || '')) return '使用中'
  if (
    [
      'pending_checkout_confirm',
      'pending_checkout_accept',
      'checkout_disputing',
      'abnormal',
    ].includes(item.latest_order_status || '') ||
    (item.latest_refund_status && item.latest_refund_status !== 'none')
  ) {
    return '售后中'
  }
  if (['completed', 'cancelled', 'closed'].includes(item.latest_order_status || '')) return '已结束'
  return '跟进中'
}

function conversationStageClass(item: ChatConversation) {
  if (item.unread_count > 0 || item.last_sender_type === 'user') return 'pending'
  if (!item.latest_order_id) return 'unjoined'
  if (item.latest_order_status === 'pending_handoff') return 'handoff'
  if (['renting', 'overdue'].includes(item.latest_order_status || '')) return 'renting'
  if (
    [
      'pending_checkout_confirm',
      'pending_checkout_accept',
      'checkout_disputing',
      'abnormal',
    ].includes(item.latest_order_status || '') ||
    (item.latest_refund_status && item.latest_refund_status !== 'none')
  ) {
    return 'after-sale'
  }
  if (['completed', 'cancelled', 'closed'].includes(item.latest_order_status || '')) return 'ended'
  return 'normal'
}

function conversationOrderText(item: ChatConversation) {
  if (item.latest_order_no) return item.latest_order_no
  if (item.latest_order_id) return `订单 ${item.latest_order_id}`
  if (item.listing_id) return `发布 ${formatListingNo('', item.listing_id)}`
  return '暂无订单'
}

function amountYuan(cent: unknown) {
  if (cent !== undefined && cent !== null) return centToYuan(Number(cent || 0))
  return 0
}

function money(value: unknown) {
  return formatMoney(Number(value || 0))
}

function moneyCent(value: number) {
  return formatCentWithSymbol(value)
}

function orderRentedAt() {
  return activeOrder.value?.rented_at
}

function orderEstimatedEndAt() {
  if (!activeOrder.value) return undefined
  if (activeOrder.value.estimated_end_at) return activeOrder.value.estimated_end_at
  const rentedAt = orderRentedAt()
  const durationHours = Number(activeOrder.value.estimated_duration_hours || 0)
  if (!rentedAt || durationHours <= 0) return undefined
  return new Date(new Date(rentedAt).getTime() + durationHours * 60 * 60 * 1000).toISOString()
}

function paymentBizTypeLabel(type: string) {
  const map: Record<string, string> = {
    order_pay: '订单支付',
    checkout_refund: '结账退款',
    arbitration_refund: '仲裁退款',
    admin_refund: '人工退款',
    cancel_refund: '取消退款',
    admin_close_refund: '客服关闭退款',
  }
  return map[type] || type
}

function formatHandoffRecordType(type: string) {
  const typeMap: Record<string, string> = {
    owner_handoff: '卖家交接',
    renter_checkout: '买家结账',
    owner_counter_checkout: '卖家反驳结账',
    renter_confirm_checkout: '买家确认结账',
    owner_accept_checkout: '卖家接受结账',
    admin_arbitration: '客服仲裁',
  }
  return typeMap[type] || type
}

function firstQueryValue(value: unknown) {
  if (Array.isArray(value)) return typeof value[0] === 'string' ? value[0] : ''
  return typeof value === 'string' ? value : ''
}
</script>

<template>
  <section class="admin-page">
    <div class="page-head">
      <div>
        <h1>客服会话</h1>
        <p>处理订单三方沟通和平台咨询</p>
      </div>
      <div class="head-right">
        <NotificationSettings scope="admin" />
        <el-button @click="quickReplyVisible = true">快捷回复管理</el-button>
        <el-button :loading="loading" @click="loadConversations()">刷新</el-button>
      </div>
    </div>

    <div class="chat-workbench">
      <aside class="conversation-pane" v-loading="loading">
        <div class="filter-tabs">
          <el-radio-group v-model="filter" size="small" @change="handleFilterChange">
            <el-radio-button v-for="item in ownershipTabs" :key="item.key" :value="item.key">
              {{ item.label }} {{ ownershipCount(item.key) }}
            </el-radio-button>
          </el-radio-group>
          <div class="stage-tabs">
            <button
              v-for="item in stageTabs"
              :key="item.key"
              type="button"
              :class="{ active: stage === item.key }"
              @click="handleStageChange(item.key)"
            >
              <span>{{ item.label }}</span>
              <em>{{ stageCount(item.key) }}</em>
            </button>
          </div>
          <el-input
            v-model="keyword"
            class="conversation-search"
            clearable
            size="small"
            placeholder="搜索群名 / 订单 / 手机号 / 备注"
            @input="handleKeywordInput"
            @clear="handleKeywordSearch"
            @keyup.enter="handleKeywordSearch"
          />
        </div>
        <button
          v-for="item in conversations"
          :key="item.id"
          type="button"
          class="conversation-row"
          :class="{ active: active?.id === item.id }"
          @click="openConversation(item)"
        >
          <div class="row-title">
            <strong>{{ getConversationTitle(item) }}</strong>
            <span>{{ formatDateMinute(item.last_message_at || item.created_at) }}</span>
          </div>
          <p>
            {{
              item.last_message_preview ||
              (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建')
            }}
          </p>
          <div class="row-tags">
            <span class="stage-tag" :class="conversationStageClass(item)">
              {{ conversationStageLabel(item) }}
            </span>
            <span>{{ conversationOrderText(item) }}</span>
          </div>
          <div class="row-meta">
            <span class="support-name">{{ getSupportName(item) }}</span>
            <em v-if="item.unread_count > 0">{{ item.unread_count }}</em>
          </div>
        </button>
        <el-empty v-if="!loading && conversations.length === 0" description="暂无客服会话" />
      </aside>

      <main class="message-pane">
        <template v-if="active">
          <header class="message-head">
            <div class="head-title">
              <template v-if="remarkEditing">
                <el-input
                  v-model="remarkValue"
                  size="small"
                  style="width: 200px"
                  placeholder="输入备注"
                  @keyup.enter="saveRemark"
                />
                <el-button size="small" type="primary" @click="saveRemark">保存</el-button>
                <el-button size="small" @click="remarkEditing = false">取消</el-button>
              </template>
              <template v-else>
                <h2>
                  {{ getConversationTitle(active) }}
                  <el-button link size="small" @click="startEditRemark">编辑备注</el-button>
                </h2>
                <p>{{ activeMembers }}</p>
              </template>
            </div>
            <div class="head-actions">
              <el-button size="small" @click="transferVisible = true">转接</el-button>
              <RouterLink v-if="activeOrder" :to="orderDetailLink">
                <el-button size="small">查看订单</el-button>
              </RouterLink>
            </div>
          </header>

          <div class="message-body" :class="{ 'has-order': hasOrderContext }">
            <div ref="listRef" class="message-list" v-loading="messageLoading">
              <div
                v-for="item in messages"
                :key="item.id"
                class="message-row"
                :class="{
                  self: item.is_self,
                  system: item.sender_type === 'system',
                  admin: item.sender_role === 'admin',
                }"
              >
                <template v-if="item.sender_type === 'system'">
                  <span>{{ item.content }}</span>
                </template>
                <template v-else>
                  <small :class="{ 'admin-label': item.sender_role === 'admin' }">
                    {{ senderLabel(item) }} · {{ formatDateMinute(item.created_at) }}
                  </small>
                  <p v-if="item.content">{{ item.content }}</p>
                  <div v-if="item.attachment_urls.length > 0" class="message-attachments">
                    <ChatAttachmentImage
                      v-for="url in item.attachment_urls"
                      :key="url"
                      :source="url"
                      admin
                    />
                  </div>
                  <small v-if="item.is_self" class="read-status" :class="{ read: item.is_read }">
                    {{ item.is_read ? '已读' : '未读' }}
                  </small>
                </template>
              </div>
            </div>

            <aside v-if="hasOrderContext" class="order-side-panel" v-loading="orderLoading">
              <template v-if="activeOrder">
                <div class="order-side-head">
                  <span>{{ activeOrder.order_no }}</span>
                  <strong>{{ activeListingCode }}</strong>
                  <p>
                    {{ activeOrder.title }} · {{ activeOrder.server_region }} /
                    {{ activeOrder.login_platform }}
                  </p>
                </div>

                <div class="order-side-metrics">
                  <div>
                    <span>订单状态</span>
                    <strong>{{ orderStatusLabel(activeOrder.status) }}</strong>
                  </div>
                  <div>
                    <span>交接状态</span>
                    <strong>{{ handoffStatusLabel(activeOrder.handoff_status) }}</strong>
                  </div>
                  <div>
                    <span>订单金额</span>
                    <strong>¥{{ money(amountYuan(activeOrder.rent_amount_cent)) }}</strong>
                  </div>
                  <div>
                    <span>押金</span>
                    <strong>¥{{ money(amountYuan(activeOrder.deposit_amount_cent)) }}</strong>
                  </div>
                </div>

                <section class="order-side-section">
                  <h3>用户信息</h3>
                  <p>商品编号：{{ activeListingCode }}</p>
                  <p>租客：{{ activeOrder.renter_phone || activeOrder.renter_id }}</p>
                  <p>号主：{{ activeOrder.owner_phone || activeOrder.owner_id }}</p>
                  <p>开始：{{ formatDateTime(orderRentedAt(), '未开始') }}</p>
                  <p>预计截止：{{ formatDateTime(orderEstimatedEndAt(), '未设置') }}</p>
                </section>

                <section class="order-side-section">
                  <h3>支付与退款</h3>
                  <template v-if="latestPaymentRecord">
                    <p>
                      {{ paymentBizTypeLabel(latestPaymentRecord.biz_type) }} ·
                      {{ moneyCent(latestPaymentRecord.amount_cent) }}
                    </p>
                    <p>
                      {{ latestPaymentRecord.provider || '-' }} ·
                      {{ latestPaymentRecord.third_order_id }}
                    </p>
                    <p>{{ formatDateTime(latestPaymentRecord.created_at) }}</p>
                  </template>
                  <p v-else>暂无支付流水</p>
                </section>

                <section class="order-side-section">
                  <h3>交接记录</h3>
                  <template v-if="latestHandoffRecord">
                    <strong>{{ formatHandoffRecordType(latestHandoffRecord.type) }}</strong>
                    <p>{{ latestHandoffRecord.content }}</p>
                    <p>{{ formatDateTime(latestHandoffRecord.created_at) }}</p>
                  </template>
                  <p v-else>暂无交接记录</p>
                </section>
              </template>
              <el-empty v-else description="暂无订单信息" />
            </aside>
          </div>

          <footer class="composer">
            <div class="composer-tools">
              <input
                ref="fileInputRef"
                class="hidden-file"
                type="file"
                accept="image/jpeg,image/png,image/webp"
                multiple
                @change="handleImageChange"
              />
              <el-dropdown trigger="click" @command="handleQuickReplySelect">
                <el-button size="small" text>快捷回复</el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item
                      v-for="reply in quickReplies"
                      :key="reply.id"
                      :command="reply"
                    >
                      <span class="reply-title">{{ reply.title }}</span>
                      <span class="reply-preview"
                        >{{ reply.content.slice(0, 30)
                        }}{{ reply.content.length > 30 ? '...' : '' }}</span
                      >
                    </el-dropdown-item>
                    <el-dropdown-item v-if="quickReplies.length === 0" disabled>
                      暂无快捷回复
                    </el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-button
                size="small"
                text
                :icon="Picture"
                :loading="uploading"
                :disabled="attachments.length >= 9"
                @click="pickImages"
              >
                图片
              </el-button>
            </div>
            <div v-if="attachments.length > 0" class="pending-attachments">
              <div v-for="(url, index) in attachments" :key="url" class="pending-item">
                <ChatAttachmentImage :source="url" admin />
                <button type="button" class="remove-attachment" @click="removeAttachment(index)">
                  <el-icon :size="14"><Close /></el-icon>
                </button>
              </div>
            </div>
            <div class="composer-input">
              <el-input
                v-model="content"
                type="textarea"
                :rows="3"
                maxlength="1000"
                show-word-limit
                placeholder="输入客服回复"
                @keydown.enter.exact.prevent="handleSend"
                @paste="handlePaste"
              />
              <el-button
                type="primary"
                :loading="sending"
                :disabled="!canSend || uploading"
                @click="handleSend"
                >发送</el-button
              >
            </div>
          </footer>
        </template>
        <el-empty v-else description="请选择会话" />
      </main>
    </div>

    <TransferDialog
      v-if="active"
      v-model="transferVisible"
      :conversation-id="active.id"
      @success="handleTransferSuccess"
    />

    <QuickReplyDialog v-model="quickReplyVisible" @success="loadQuickReplies" />
  </section>
</template>

<style scoped>
.admin-page {
  display: grid;
  height: 100%;
  min-height: 0;
  grid-template-rows: auto minmax(0, 1fr);
}

.page-head {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.page-head h1 {
  margin: 0;
  font-size: 22px;
}

.page-head p {
  margin: 6px 0 0;
  color: #6b7280;
}

.head-right {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.chat-workbench {
  display: grid;
  min-height: 0;
  grid-template-columns: 330px minmax(0, 1fr);
  overflow: hidden;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #fff;
}

.conversation-pane {
  min-height: 0;
  overflow-y: auto;
  border-right: 1px solid #e5e7eb;
  background: #f8fafc;
}

.filter-tabs {
  display: grid;
  gap: 10px;
  padding: 10px 12px 12px;
  border-bottom: 1px solid #e5e7eb;
}

.filter-tabs :deep(.el-radio-group) {
  display: flex;
  width: 100%;
}

.filter-tabs :deep(.el-radio-button) {
  flex: 1;
}

.filter-tabs :deep(.el-radio-button__inner) {
  width: 100%;
  padding: 7px 8px;
}

.stage-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.stage-tabs button {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 8px;
  border: 1px solid #d8dee8;
  border-radius: 999px;
  background: #fff;
  color: #4b5563;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.stage-tabs button.active {
  border-color: #3b82f6;
  background: #eff6ff;
  color: #2563eb;
}

.stage-tabs em {
  color: #94a3b8;
  font-style: normal;
  font-weight: 800;
}

.stage-tabs button.active em {
  color: #2563eb;
}

.conversation-search {
  width: 100%;
}

.conversation-row {
  position: relative;
  display: block;
  width: 100%;
  padding: 14px;
  border: 0;
  border-bottom: 1px solid #e5e7eb;
  background: transparent;
  text-align: left;
}

.conversation-row.active {
  background: #eef6ff;
}

.row-title {
  display: flex;
  gap: 10px;
  align-items: center;
}

.row-title strong {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  color: #111827;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-title span {
  flex: none;
  color: #9ca3af;
  font-size: 12px;
}

.conversation-row p {
  margin: 8px 24px 0 0;
  overflow: hidden;
  color: #6b7280;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-tags {
  display: flex;
  min-width: 0;
  gap: 6px;
  align-items: center;
  margin-top: 8px;
}

.row-tags span {
  overflow: hidden;
  max-width: 160px;
  padding: 2px 7px;
  border-radius: 999px;
  background: #eef2f7;
  color: #64748b;
  font-size: 11px;
  line-height: 18px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-tags .stage-tag {
  flex: none;
  max-width: none;
  font-weight: 700;
}

.stage-tag.pending {
  background: #fee2e2;
  color: #dc2626;
}

.stage-tag.unjoined {
  background: #fef3c7;
  color: #b45309;
}

.stage-tag.handoff {
  background: #dbeafe;
  color: #2563eb;
}

.stage-tag.renting {
  background: #dcfce7;
  color: #15803d;
}

.stage-tag.after-sale {
  background: #f3e8ff;
  color: #7e22ce;
}

.stage-tag.ended {
  background: #e5e7eb;
  color: #4b5563;
}

.row-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 8px;
}

.support-name {
  color: #8a94a6;
  font-size: 12px;
}

.row-meta em {
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-style: normal;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

.message-pane {
  display: grid;
  min-width: 0;
  min-height: 0;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.message-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid #e5e7eb;
}

.head-title {
  flex: 1;
  min-width: 0;
}

.head-title h2 {
  margin: 0;
  font-size: 18px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.head-title p {
  margin: 6px 0 0;
  color: #6b7280;
  font-size: 13px;
}

.head-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.head-actions a {
  text-decoration: none;
}

.message-body {
  display: grid;
  min-width: 0;
  min-height: 0;
  background: #f3f6fa;
}

.message-body.has-order {
  grid-template-columns: minmax(0, 1fr) minmax(330px, 38%);
}

.message-list {
  min-height: 0;
  overflow-y: auto;
  padding: 18px;
  background: #f3f6fa;
}

.order-side-panel {
  min-width: 0;
  overflow-y: auto;
  padding: 16px;
  border-left: 1px solid #e5e7eb;
  background: #f8fafc;
}

.order-side-head,
.order-side-section {
  padding: 14px;
  border: 1px solid #edf2f7;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 8px 18px rgba(15, 23, 42, 0.03);
}

.order-side-head {
  display: grid;
  gap: 6px;
  margin-bottom: 12px;
}

.order-side-head span {
  color: #2563eb;
  font-size: 12px;
  font-weight: 700;
}

.order-side-head strong {
  color: #071947;
  font-size: 18px;
}

.order-side-head p,
.order-side-section p {
  margin: 0;
  color: #7c8aaa;
  font-size: 13px;
  line-height: 1.6;
}

.order-side-metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}

.order-side-metrics div {
  display: grid;
  gap: 5px;
  min-width: 0;
  padding: 12px;
  border: 1px solid #edf2f7;
  border-radius: 8px;
  background: #fff;
}

.order-side-metrics span {
  color: #8a94a6;
  font-size: 12px;
}

.order-side-metrics strong {
  overflow: hidden;
  color: #071947;
  font-size: 16px;
  font-weight: 800;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.order-side-section {
  display: grid;
  gap: 8px;
}

.order-side-section + .order-side-section {
  margin-top: 12px;
}

.order-side-section h3 {
  margin: 0;
  color: #071947;
  font-size: 15px;
}

.order-side-section strong {
  color: #071947;
  font-size: 13px;
}

.message-row {
  max-width: 70%;
  margin-bottom: 14px;
}

.message-row.self {
  margin-left: auto;
  text-align: right;
}

.message-row.system {
  max-width: none;
  text-align: center;
}

.message-row small {
  display: block;
  margin-bottom: 5px;
  color: #8a94a6;
}

.read-status {
  margin-top: 4px;
  margin-bottom: 0;
  font-size: 11px;
}

.read-status.read {
  color: #67c23a;
}

.message-row p {
  display: inline-block;
  margin: 0;
  padding: 10px 12px;
  border-radius: 8px;
  background: #fff;
  color: #111827;
  line-height: 1.5;
  text-align: left;
}

.message-row.self p {
  background: #dff5eb;
}

.message-row.admin p {
  background: #fff4e6;
  border-left: 3px solid #ff9800;
}

.admin-label {
  color: #ff9800 !important;
  font-weight: 600;
}

.message-attachments {
  display: grid;
  justify-items: start;
  gap: 6px;
}

.message-row.self .message-attachments {
  justify-items: end;
}

.message-row.system span {
  display: inline-block;
  padding: 5px 10px;
  border-radius: 8px;
  background: #e5e7eb;
  color: #6b7280;
  font-size: 12px;
}

.composer {
  border-top: 1px solid #e5e7eb;
}

.composer-tools {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 14px;
  border-bottom: 1px solid #f0f0f0;
}

.hidden-file {
  display: none;
}

.pending-attachments {
  display: flex;
  gap: 8px;
  padding: 10px 14px 0;
  overflow-x: auto;
}

.pending-item {
  position: relative;
  flex: none;
}

.pending-item :deep(.chat-image-button) {
  width: 84px;
  height: 84px;
}

.pending-item :deep(.chat-image-button img) {
  height: 84px;
}

.remove-attachment {
  position: absolute;
  top: 4px;
  right: 4px;
  display: grid;
  width: 22px;
  height: 22px;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(17, 24, 39, 0.72);
  color: #fff;
  cursor: pointer;
}

.composer-input {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 88px;
  gap: 12px;
  align-items: end;
  padding: 14px;
}

.reply-title {
  font-weight: 500;
  margin-right: 8px;
}

.reply-preview {
  color: #9ca3af;
  font-size: 12px;
}
</style>
