<template>
  <Teleport to="body">
    <div
      v-show="visible"
      ref="chatWindowRef"
      class="chat-floating-window"
      :class="{ 'chat-floating-window--minimized': isMinimized }"
      :style="windowStyle"
    >
      <div
        class="chat-window-header"
        @mousedown="onHeaderMouseDown"
      >
        <span class="chat-window-title" :title="patientName">与 {{ patientName }} 通讯</span>
        <div class="chat-header-actions" @mousedown.stop>
          <el-button type="danger" link class="chat-close-btn" @click="handleClose">
            <el-icon><Close /></el-icon>
          </el-button>
          <el-tooltip v-if="!isMinimized" content="缩小为标题条，不关闭会话" placement="top">
            <el-button type="primary" link class="chat-minimize-btn" @click="isMinimized = true">
              <el-icon><Fold /></el-icon>
              <span class="chat-header-btn-text">缩小</span>
            </el-button>
          </el-tooltip>
          <el-tooltip v-else content="展开聊天窗口" placement="top">
            <el-button type="primary" link class="chat-expand-btn" @click="isMinimized = false">
              <el-icon><Expand /></el-icon>
              <span class="chat-header-btn-text">展开</span>
            </el-button>
          </el-tooltip>
        
        </div>
      </div>
      <div v-show="!isMinimized" class="chat-container">
      <div v-if="!isReady" class="loading-container">
        <el-icon class="is-loading"><Loading /></el-icon>
        <span>{{ loadingText }}</span>
      </div>
      <div v-else-if="error" class="error-container">
        <el-alert :title="error" type="error" :closable="false" />
      </div>
      <div v-else class="chat-content">
        <UIKitProvider language="zh-CN" theme="light">
          <div class="chat-layout">
            <!-- 嵌入式问诊窗口仅与当前患者会话，不展示全局会话列表 -->
            <Chat class="chat-area">
              <MessageList :conversationID="conversationId" :Message="ChatMessageItem" />
              <MessageInput>
                <template #headerToolbar>
                  <div class="message-toolbar">
                    <EmojiPicker />
                    <ImagePicker />
                    <FilePicker />
                    <!-- 仅在 TUICallKit 初始化成功后展示音视频入口，避免“初始化登录未完成”错误 -->
                    <AudioCallPicker v-if="isCallReady" />
                    <!-- 视频接通后 doBindCallRoom → 后端 bindCallRoom 会调 CreateCloudRecording，RecordParams.RecordMode=2（混流/合流），见 TrtcCloudRecordingService -->
                    <VideoCallPicker v-if="isCallReady" />
                    <!-- 群组视频通话（基于 TUICallKitServer.calls，多人通话入口） -->
                    <el-button
                      v-if="isCallReady"
                      size="small"
                      type="primary"
                      @click="startGroupVideoCall"
                    >
                      群视频
                    </el-button>
                  </div>
                </template>
              </MessageInput>
            </Chat>
          </div>
        </UIKitProvider>
      </div>
    </div>
      <!-- 挂载 TUICallKit 组件（可拖动，仅在通话时显示） -->
      <div
        v-if="isCallReady"
        v-show="showCallKitWindow"
        ref="callKitWrapperRef"
        class="chat-dialog-call-kit-wrapper"
        :style="callKitStyle"
      >
        <div
          class="call-kit-drag-handle"
          @mousedown="onCallKitDragStart"
        >
          <div class="call-kit-drag-handle-left">
            <span class="drag-handle-text">视频通话</span>
            <span v-if="localRecordingBadge" class="call-local-recording-hint">本地录制中</span>
          </div>
         <div class="call-kit-drag-handle-actions">
             <el-button
              type="primary"
              link
              class="call-kit-screenshot-btn"
              :loading="captureUploading"
              :disabled="captureUploading"
              @mousedown.stop
              @click.stop.prevent="handleCallKitScreenshot"
            >
              <el-icon><Camera /></el-icon>
              <span class="call-kit-screenshot-text">截屏</span>
            </el-button>
            <el-button
              type="danger"
              link
              class="call-kit-close-btn"
              @mousedown.stop
              @click.stop="handleCallKitClose"
            >
              <el-icon><Close /></el-icon>
            </el-button>
            <el-icon class="drag-handle-icon"><Rank /></el-icon>
          </div>
        </div>
        <div ref="callKitContentRef" class="call-kit-content">
          <TUICallKit
            :allowedMinimized="true"
            :allowedFullScreen="true"
            class="chat-dialog-call-kit"
          />
        </div>
      </div>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
import { ref, nextTick, watch, computed, onMounted, onUnmounted } from 'vue'
import { Loading, Close, Rank, Camera, Fold, Expand } from '@element-plus/icons-vue'
import { uploadImageBlob, uploadVideoBlob } from '@/api/file'
import { addDoctorNote } from '@/api/patient'
import {
  attachLocalCallRecording,
  bindCallRoom,
  endCall,
  getCallSignature,
  startCall,
  tcmDiagnosisDetail,
  tcmDiagnosisEdit
} from '@/api/tcm'
import { CallLocalRecorder } from '@/utils/call-local-recorder'
import { captureVideoFrameFromElement } from '@/utils/call-video-screenshot'
import {
    DIAGNOSIS_TONGUE_IMAGES_UPDATED,
    type DiagnosisTongueImagesUpdatedDetail
} from '@/utils/diagnosis-sync-events'
import feedback from '@/utils/feedback'
import {
  formatTUICallUserError,
  getTUICallPackageArrearsMessage,
  isTUICallPackageAbilityError
} from '@/utils/tuicall-error'
import useUserStore from '@/stores/modules/user'
import {
  Chat,
  MessageList,
  MessageInput,
  UIKitProvider,
  useLoginState,
  useConversationListState,
  EmojiPicker,
  ImagePicker,
  FilePicker,
  AudioCallPicker,
  VideoCallPicker
} from '@tencentcloud/chat-uikit-vue3'
import ChatMessageItem from './ChatMessageItem.vue'
import TencentCloudChat from '@tencentcloud/chat'
import TUIChatEngine from '@tencentcloud/chat-uikit-engine'
import { imCustomDataIndicatesVideoHangup } from '@/utils/im-call-hangup-detect'
import '@/assets/chat-uikit.css'
import {
  TUICallKit,
  TUICallKitServer,
  TUICallType,
  TUIStore,
  StoreName,
  NAME
} from '@tencentcloud/call-uikit-vue'
import { TUICallEvent } from '@tencentcloud/call-engine-js'
const assistant_ids=ref('');
/** 缩小为仅标题条，便于医生操作后台其他页面（不登出、不挂断） */
const isMinimized = ref(false)
const visible = ref(false)
const isReady = ref(false)
const error = ref('')
const patientName = ref('')
const patientId = ref<number | null>(null)
const diagnosisId = ref<number | null>(null)
const loadingText = ref('正在初始化...')
const patientUserId = ref('')
const conversationId = ref('')
const isCallReady = ref(false)
const showCallKitWindow = ref(false)  // 仅在通话中显示视频窗口
const { login, logout } = useLoginState()
const { setActiveConversation, createC2CConversation, activeConversation } = useConversationListState()
const userStore = useUserStore()

/** TUICallKit CallStatus（与 @tencentcloud/call-uikit-vue 一致） */
const CALL_STATUS_IDLE = 'idle'
const CALL_STATUS_CALLING = 'calling'
const CALL_STATUS_CONNECTED = 'connected'

/** 通话绑定 TRTC 房间号到诊单（云端录制回调按 room_id 关联） */
const lastBoundRoomKey = ref('')
let tearDownCallRoomBinding: (() => void) | null = null
let bindRoomRetryTimer: ReturnType<typeof setTimeout> | null = null
let bindRoomRetryAttempts = 0
const BIND_ROOM_MAX_ATTEMPTS = 40
const BIND_ROOM_RETRY_MS = 100

const localCallRecorder = new CallLocalRecorder()
/** 与 server .env [trtc] ISLOCHOSTVOD 一致：由 getCallSignature 下发，false 时不做浏览器本地录制 */
const isLochostVodEnabled = ref(false)
const localRecordingBadge = ref(false)
let localRecordingRound = 0
let startLocalRecTimer: ReturnType<typeof setTimeout> | null = null
let finalizeLocalRecordingPromise: Promise<void> | null = null
/** 本次振铃/通话对应的 startCall 落库（beforeCalling 发起，bind/上传前须 await） */
let pendingCallRecordStart: Promise<void> = Promise.resolve()
/** 接通前快照诊单 ID，避免会话切换等导致上传阶段 diagnosisId 丢失 */
let recordingDiagnosisIdSnapshot: number | null = null

function clearLocalRecordingStartTimer() {
  if (startLocalRecTimer) {
    clearTimeout(startLocalRecTimer)
    startLocalRecTimer = null
  }
}

function syncLochostVodFromSignature(res: { isLochostVod?: boolean }) {
  isLochostVodEnabled.value = Boolean(res?.isLochostVod)
}

function finalizeLocalRecordingAndAttach(): Promise<void> {
  if (finalizeLocalRecordingPromise) {
    return finalizeLocalRecordingPromise
  }
  finalizeLocalRecordingPromise = (async () => {
    clearLocalRecordingStartTimer()
    localRecordingBadge.value = false
    if (!isLochostVodEnabled.value) {
      return
    }
    localCallRecorder.beginStopNow()
    const id = recordingDiagnosisIdSnapshot ?? diagnosisId.value
    try {
      await pendingCallRecordStart
    } catch {
      //
    }
    const blob = await localCallRecorder.stop()
    if (!blob) {
      console.warn(
        '[chat-dialog] 本地录制无有效文件（若通话较长仍出现，请换 Chrome 并看控制台是否有 MediaRecorder 报错）'
      )
      return
    }
    if (id == null) {
      feedback.msgWarning('缺少诊单信息，本地录制无法保存到通话记录')
      return
    }
    try {
      const data = await uploadVideoBlob(blob, `call-rec-${id}-${Date.now()}.webm`)
      const fileUrl = data.uri || data.url
      if (!fileUrl) {
        feedback.msgError('上传成功但未返回文件地址')
        return
      }
      await attachLocalCallRecording({ diagnosis_id: id, file_url: fileUrl })
      feedback.msgSuccess('本地录制已上传并关联到通话记录')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : String(e)
      console.warn('[chat-dialog] 本地录制上传/关联失败', e)
      feedback.msgError(msg || '本地录制上传失败')
    }
  })().finally(() => {
    finalizeLocalRecordingPromise = null
  })
  return finalizeLocalRecordingPromise
}

/** 从 TUIStore 读取房间号；默认 0 表示尚未分配 */
function readRoomIdFromStore(): string {
  const raw = TUIStore.getData(StoreName.CALL, NAME.ROOM_ID)
  if (raw === null || raw === undefined) return ''
  if (typeof raw === 'number') {
    if (raw === 0) return ''
    return String(raw)
  }
  const s = String(raw).trim()
  if (!s || s === '0') return ''
  return s
}

/** 引擎实例上可能存在房间号（不同版本字段名不一，仅作兜底） */
function readRoomIdFromEngineFallback(): string {
  try {
    const eng = TUICallKitServer.getTUICallEngineInstance?.() as Record<string, unknown> | null
    if (!eng) return ''
    const candidates = [eng.roomId, eng.roomID, eng._roomId, eng._roomID, eng.strRoomID]
    for (const c of candidates) {
      if (c === null || c === undefined || c === '') continue
      const s = String(c).trim()
      if (s && s !== '0') return s
    }
  } catch {
    //
  }
  return ''
}

function readAnyRoomId(): string {
  return readRoomIdFromStore() || readRoomIdFromEngineFallback()
}

/** 从引擎 calls/call/groupCall 的 Promise 返回值里解析房间号（含 data 嵌套） */
function normalizeRoomPair(roomID: unknown, strRoomID: unknown): string {
  if (strRoomID != null && String(strRoomID).trim() !== '' && String(strRoomID) !== '0') {
    return String(strRoomID).trim()
  }
  if (typeof roomID === 'number' && roomID > 0) return String(roomID)
  if (typeof roomID === 'string') {
    const t = roomID.trim()
    if (t && t !== '0') return t
  }
  return ''
}

function extractRoomFromEngineResult(ret: unknown): string {
  if (ret == null || typeof ret !== 'object') return ''
  const r = ret as Record<string, unknown>
  const direct =
    normalizeRoomPair(r.roomID, r.strRoomID) ||
    normalizeRoomPair(r.roomId, r.strRoomId)
  if (direct) return direct
  const d = r.data
  if (d && typeof d === 'object') {
    const dd = d as Record<string, unknown>
    const nested =
      normalizeRoomPair(dd.roomID, dd.strRoomID) ||
      normalizeRoomPair(dd.roomId, dd.strRoomId)
    if (nested) return nested
  }
  return ''
}

function clearBindRoomRetry() {
  if (bindRoomRetryTimer) {
    clearTimeout(bindRoomRetryTimer)
    bindRoomRetryTimer = null
  }
  bindRoomRetryAttempts = 0
}

/** 在 calling / connected 阶段轮询房间号（ROOM_ID 可能比 status 晚一拍） */
function scheduleBindRoomRetry() {
  clearBindRoomRetry()
  const tick = () => {
    bindRoomRetryTimer = null
    const status = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS)
    if (status === CALL_STATUS_IDLE) return
    if (status !== CALL_STATUS_CALLING && status !== CALL_STATUS_CONNECTED) return
    const rid = readAnyRoomId()
    if (rid) {
      void doBindCallRoom(rid)
      return
    }
    bindRoomRetryAttempts++
    if (bindRoomRetryAttempts >= BIND_ROOM_MAX_ATTEMPTS) {
      console.warn(
        '[chat-dialog] 仍未解析到有效房间号（已重试',
        BIND_ROOM_MAX_ATTEMPTS,
        '次）'
      )
      return
    }
    bindRoomRetryTimer = setTimeout(tick, BIND_ROOM_RETRY_MS)
  }
  bindRoomRetryTimer = setTimeout(tick, 0)
}

/**
 * 在 TUICallKitServer.call / calls / groupCall 执行完毕后，引擎已返回 roomId，
 * _updateCallStoreAfterCall 会写入 TUIStore；工具栏多走 call() 而非 calls()，必须 hook 这三者。
 */
async function flushAndBindRoomAfterTUICallApi(apiName: string) {
  if (patientId.value == null || diagnosisId.value == null) return
  await nextTick()
  for (let i = 0; i < 40; i++) {
    const rid = readAnyRoomId()
    if (rid) {
      await doBindCallRoom(rid)
      return
    }
    await new Promise((r) => setTimeout(r, 50))
  }
  console.warn('[chat-dialog] 发起通话后仍未解析到房间号（API:', apiName, ')')
}

/**
 * 在 TUICallEngine 上包装 calls/call/groupCall：房间号往往在 Promise 返回值里，
 * 仅靠 TUIStore 轮询可能拿不到或晚一拍。
 */
function installEngineRoomCapture() {
  const w = window as unknown as Record<string, unknown>
  if (w.__zytEngineRoomCapture__) return
  const eng = TUICallKitServer.getTUICallEngineInstance?.() as
    | (Record<string, unknown> & { __zytEngineRoomCapture__?: boolean })
    | null
    | undefined
  if (!eng || typeof eng.calls !== 'function') return
  w.__zytEngineRoomCapture__ = true
  eng.__zytEngineRoomCapture__ = true
  const wrap = (methodName: 'calls' | 'call' | 'groupCall') => {
    const original = eng[methodName]
    if (typeof original !== 'function') return
    eng[methodName] = async function patched(this: unknown, ...args: unknown[]) {
      const ret = await (original as (...a: unknown[]) => Promise<unknown>).apply(eng, args)
      const fromRet = extractRoomFromEngineResult(ret)
      if (fromRet) {
        void doBindCallRoom(fromRet)
      } else if (import.meta.env.DEV) {
        console.debug('[chat-dialog] engine.' + methodName + ' 返回未含 roomID，将回退读 Store', ret)
      }
      await flushAndBindRoomAfterTUICallApi('engine.' + methodName)
      return ret
    }
  }
  wrap('calls')
  wrap('call')
  wrap('groupCall')
}

/** hangup 时 SDK 会立刻 exitRoom；在 hangup 入口同步停录，避免轨道被掐断后无数据 */
function installHangupPreStopHook() {
  const w = window as unknown as Record<string, unknown>
  if (w.__zytHangupPreStop__) return
  const server = TUICallKitServer as unknown as Record<string, unknown> & {
    hangup?: (...args: unknown[]) => unknown
  }
  const raw = server.hangup
  if (typeof raw !== 'function') return
  w.__zytHangupPreStop__ = true
  server.hangup = function patchedHangup(this: unknown, ...args: unknown[]) {
    localCallRecorder.beginStopNow()
    return raw.apply(TUICallKitServer, args)
  }
}

/**
 * UI 挂断时往往先调 TRTC stopLocalVideo/stopLocalAudio，再 hangup；必须在它们之前停 MediaRecorder。
 */
function installTrtcCloudPreStopHook() {
  const w = window as unknown as Record<string, unknown>
  if (w.__zytTrtcPreStop__) return
  const eng = TUICallKitServer.getTUICallEngineInstance?.() as
    | { trtcCloud?: Record<string, unknown> }
    | null
    | undefined
  const trtc = eng?.trtcCloud
  if (!trtc || typeof trtc !== 'object') return
  let patched = 0
  const methods = ['stopLocalVideo', 'stopLocalAudio'] as const
  for (const methodName of methods) {
    const orig = trtc[methodName]
    if (typeof orig !== 'function') continue
    trtc[methodName] = function patchedTrtc(this: unknown, ...args: unknown[]) {
      localCallRecorder.beginStopNow()
      return (orig as (...a: unknown[]) => unknown).apply(trtc, args)
    }
    patched++
  }
  if (patched > 0) {
    w.__zytTrtcPreStop__ = true
  }
}

let engineRoomEventHandlers: Array<{ event: TUICallEvent; fn: (e: unknown) => void }> = []

/** IM MESSAGE_RECEIVED：对端挂断等信令走 TIM，与引擎事件双通道；需在 reportCallEnded 之后绑定 */
let imHangupMessageHandler: ((event: { data?: unknown }) => void) | null = null
let lastHangupCloudStopAt = 0
const HANGUP_CLOUD_STOP_DEBOUNCE_MS = 1500

/** 将 tcm_call_record 置结束并 DeleteCloudRecording（合流停录） */
async function reportCallEnded() {
  const id = recordingDiagnosisIdSnapshot ?? diagnosisId.value
  if (id == null) {
    console.warn('[chat-dialog] endCall 跳过：无诊单 ID（快照与当前均为空）')
    return
  }
  try {
    await endCall({ diagnosis_id: id })
    console.log('[chat-dialog] endCall 已同步', { diagnosis_id: id })
  } catch (e) {
    console.warn('[chat-dialog] endCall 失败', e)
  }
}

/** 引擎 CALL_END / IM 挂断信令共用防抖，避免重复 DeleteCloudRecording */
function debouncedStopRecordingAndEndCloud(source: string) {
  const now = Date.now()
  if (now - lastHangupCloudStopAt < HANGUP_CLOUD_STOP_DEBOUNCE_MS) return
  lastHangupCloudStopAt = now
  console.log('[chat-dialog] 挂断停合流:', source)
  if (localCallRecorder.isRecording()) {
    localCallRecorder.beginStopNow()
  }
  void reportCallEnded()
}

function bindImHangupMessageListener() {
  unbindImHangupMessageListener()
  try {
    const chat = TUIChatEngine?.chat as { on?: (ev: string, fn: (e: { data?: unknown }) => void) => void; off?: (ev: string, fn: (e: { data?: unknown }) => void) => void } | undefined
    if (!chat?.on) {
      console.warn('[chat-dialog] TUIChatEngine.chat 不可用，无法监听 IM 视频挂断信令')
      return
    }
    imHangupMessageHandler = (event: { data?: unknown }) => {
      if (!visible.value) return
      const pu = patientUserId.value
      if (!pu) return
      const expectConv = pu.startsWith('C2C') ? pu : `C2C${pu}`
      const list = Array.isArray(event.data) ? event.data : []
      for (const m of list as Array<Record<string, unknown>>) {
        if (!m || m.conversationID !== expectConv) continue
        const t = m.type as string | number | undefined
        const isCustom =
          t === TencentCloudChat.TYPES.MSG_CUSTOM ||
          t === 'TIMCustomElem' ||
          (typeof t === 'string' && /custom/i.test(t))
        if (!isCustom) continue
        const payload = m.payload as Record<string, unknown> | undefined
        const raw = typeof payload?.data === 'string' ? payload.data : ''
        if (!raw || !imCustomDataIndicatesVideoHangup(raw)) continue
        debouncedStopRecordingAndEndCloud('IM')
        break
      }
    }
    chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, imHangupMessageHandler)
  } catch (e) {
    console.warn('[chat-dialog] 绑定 IM 挂断监听失败', e)
  }
}

function unbindImHangupMessageListener() {
  if (!imHangupMessageHandler) return
  try {
    const chat = TUIChatEngine?.chat as { off?: (ev: string, fn: (e: { data?: unknown }) => void) => void } | undefined
    chat?.off?.(TencentCloudChat.EVENT.MESSAGE_RECEIVED, imHangupMessageHandler)
  } catch {
    //
  }
  imHangupMessageHandler = null
}

function bindEngineRoomEvents() {
  try {
    type EngineWithEvents = Record<string, unknown> & {
      __zytRoomEvents?: boolean
      on?: (event: TUICallEvent | string, fn: (e: unknown) => void) => void
    }
    const eng = TUICallKitServer.getTUICallEngineInstance?.() as EngineWithEvents | null | undefined
    const subscribe = eng?.on
    if (!eng || typeof subscribe !== 'function' || eng.__zytRoomEvents) return
    eng.__zytRoomEvents = true
    const onRoomHint = (ev: unknown) => {
      const fromEv = extractRoomFromEngineResult(ev)
      if (fromEv) {
        void doBindCallRoom(fromEv)
        return
      }
      void tryBindRoomIfReady()
    }
    const onEarlyStopRecording = () => {
      debouncedStopRecordingAndEndCloud('TUICallEngine')
    }
    const earlyStopEvents = [
      TUICallEvent.CALL_END,
      TUICallEvent.USER_LEAVE,
      TUICallEvent.ON_CALL_NOT_CONNECTED
    ] as const
    for (const event of earlyStopEvents) {
      subscribe.call(eng, event, onEarlyStopRecording)
      engineRoomEventHandlers.push({ event, fn: onEarlyStopRecording })
    }
    const events = [TUICallEvent.USER_ENTER, TUICallEvent.ON_CALL_BEGIN] as const
    for (const event of events) {
      subscribe.call(eng, event, onRoomHint)
      engineRoomEventHandlers.push({ event, fn: onRoomHint })
    }
  } catch (e) {
    console.warn('[chat-dialog] 绑定引擎房间事件失败', e)
  }
}

function unbindEngineRoomEvents() {
  try {
    const eng = TUICallKitServer.getTUICallEngineInstance?.() as
      | ({ off?: (e: TUICallEvent, fn: (ev: unknown) => void) => void } & {
          __zytRoomEvents?: boolean
        })
      | null
      | undefined
    if (!eng?.off) {
      engineRoomEventHandlers = []
      return
    }
    for (const { event, fn } of engineRoomEventHandlers) {
      eng.off(event, fn)
    }
    eng.__zytRoomEvents = false
  } catch {
    //
  }
  engineRoomEventHandlers = []
}

function installTUICallKitRoomHooks() {
  const w = window as unknown as Record<string, unknown>
  if (w.__zytTUICallRoomHooks__) return
  w.__zytTUICallRoomHooks__ = true
  const server = TUICallKitServer as unknown as Record<string, (...args: unknown[]) => Promise<unknown>>
  const wrap = (methodName: 'calls' | 'call' | 'groupCall') => {
    const original = server[methodName]
    if (typeof original !== 'function') return
    server[methodName] = async function patched(...args: unknown[]) {
      const result = await original.apply(server, args)
      await flushAndBindRoomAfterTUICallApi(methodName)
      return result
    }
  }
  wrap('calls')
  wrap('call')
  wrap('groupCall')
}

async function doBindCallRoom(rid: string) {
  try {
    await pendingCallRecordStart
  } catch {
    //
  }
  if (patientId.value == null || diagnosisId.value == null) return
  const key = `${diagnosisId.value}:${rid}`
  if (key === lastBoundRoomKey.value) {
    clearBindRoomRetry()
    return
  }
  lastBoundRoomKey.value = key
  try {
    const bindRes = (await bindCallRoom({
      diagnosis_id: diagnosisId.value,
      room_id: rid
    })) as { cloud_recording?: { started?: boolean; task_id?: string; message?: string } } | undefined
    clearBindRoomRetry()
    console.log('[chat-dialog] bindCallRoom 成功', { room_id: rid, bindRes })
    const cr = bindRes?.cloud_recording
    if (cr && cr.started === false && cr.message) {
      feedback.msgWarning(
        `云端合流录制未启动（关闭控制台全局录制后仅依赖 API）：${cr.message}`
      )
    }
  } catch (e) {
    console.warn('[chat-dialog] bindCallRoom 失败', e)
    lastBoundRoomKey.value = ''
  }
}

async function tryBindRoomIfReady() {
  if (patientId.value == null || diagnosisId.value == null) return
  const status = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS)
  if (status === CALL_STATUS_IDLE) {
    clearBindRoomRetry()
    lastBoundRoomKey.value = ''
    return
  }
  if (status !== CALL_STATUS_CALLING && status !== CALL_STATUS_CONNECTED) return

  await nextTick()
  const rid = readAnyRoomId()
  if (rid) {
    await doBindCallRoom(rid)
    return
  }
  scheduleBindRoomRetry()
}

async function ensureCallRecordStarted() {
  if (patientId.value == null || diagnosisId.value == null) return
  try {
    await startCall({
      diagnosis_id: diagnosisId.value,
      patient_id: patientId.value,
      call_type: 2
    })
  } catch (e) {
    console.warn('[chat-dialog] startCall 记录失败', e)
  }
}

function setupCallRoomBinding() {
  tearDownCallRoomBinding?.()
  let previousCallStatus =
    (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) as string | undefined) ?? CALL_STATUS_IDLE

  const onCallStatus = (newData?: string) => {
    const next = (newData as string | undefined) ?? CALL_STATUS_IDLE
    if (next === CALL_STATUS_IDLE) {
      if (previousCallStatus === CALL_STATUS_CALLING || previousCallStatus === CALL_STATUS_CONNECTED) {
        localCallRecorder.beginStopNow()
      }
      clearLocalRecordingStartTimer()
      localRecordingBadge.value = false
      if (previousCallStatus === CALL_STATUS_CALLING || previousCallStatus === CALL_STATUS_CONNECTED) {
        void (async () => {
          await reportCallEnded()
          showCallKitWindow.value = false
          await finalizeLocalRecordingAndAttach()
        })()
      }
      clearBindRoomRetry()
      lastBoundRoomKey.value = ''
      previousCallStatus = next
      return
    }
    if (next === CALL_STATUS_CONNECTED) {
      void tryBindRoomIfReady()
      clearLocalRecordingStartTimer()
      if (!isLochostVodEnabled.value) {
        previousCallStatus = next
        return
      }
      const roundAtSchedule = localRecordingRound

      // 立即尝试启动录制，不要等待 1200ms
      const attemptStartRecording = (retryCount = 0) => {
        if (roundAtSchedule !== localRecordingRound) {
          console.log('[chat-dialog] 录制轮次已变化，取消启动')
          return
        }
        
        const root = callKitContentRef.value
        const st = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) as string | undefined
        if (!root || st !== CALL_STATUS_CONNECTED) {
          console.warn('[chat-dialog] 录制启动条件不满足', { root: !!root, status: st })
          return
        }
        
        console.log(`[chat-dialog] 尝试启动本地录制（第 ${retryCount + 1} 次）`)
        
        // 检查视频元素
        let videos = Array.from(root.querySelectorAll('video')) as HTMLVideoElement[]
        
        // 如果容器内没有，检查 body（TUICallKit Teleport）
        if (videos.length === 0) {
          const bodyVideos = Array.from(document.body.querySelectorAll('video')) as HTMLVideoElement[]
          videos = bodyVideos.filter(v => {
            const so = v.srcObject
            if (!(so instanceof MediaStream)) return false
            return so.getVideoTracks().some(t => t.readyState === 'live')
          })
          console.log('[chat-dialog] 在 body 中找到活跃视频:', videos.length)
        }
        
        if (videos.length === 0) {
          if (retryCount < 10) {
            console.warn('[chat-dialog] 未找到视频元素，300ms 后重试')
            startLocalRecTimer = setTimeout(() => attemptStartRecording(retryCount + 1), 300)
          } else {
            console.error('[chat-dialog] 重试 10 次后仍未找到视频元素，放弃录制')
          }
          return
        }
        
        // 检查视频是否有有效数据
        const hasReadyVideo = videos.some(v => v.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA)
        if (!hasReadyVideo) {
          if (retryCount < 10) {
            console.warn('[chat-dialog] 视频元素未就绪，300ms 后重试')
            startLocalRecTimer = setTimeout(() => attemptStartRecording(retryCount + 1), 300)
          } else {
            console.error('[chat-dialog] 重试 10 次后视频仍未就绪，放弃录制')
          }
          return
        }
        
        console.log('[chat-dialog] 视频元素已就绪，立即启动录制')
        const ok = localCallRecorder.start(root)
        localRecordingBadge.value = ok
        if (!ok) {
          console.error('[chat-dialog] 本地录制启动失败（MediaRecorder 或画布初始化失败）')
        } else {
          console.log('[chat-dialog] ✅ 本地录制已成功启动')
        }
      }
      
      // 延迟 500ms 后开始尝试（给视频元素一点加载时间）
      startLocalRecTimer = setTimeout(() => attemptStartRecording(0), 500)
    } else {
      clearLocalRecordingStartTimer()
    }
    previousCallStatus = next
  }
  const onRoomId = () => {
    void tryBindRoomIfReady()
  }
  TUIStore.watch(StoreName.CALL, {
    [NAME.CALL_STATUS]: onCallStatus,
    [NAME.ROOM_ID]: onRoomId
  })
  tearDownCallRoomBinding = () => {
    try {
      TUIStore.unwatch(StoreName.CALL, {
        [NAME.CALL_STATUS]: onCallStatus,
        [NAME.ROOM_ID]: onRoomId
      })
    } catch {
      //
    }
    clearBindRoomRetry()
    tearDownCallRoomBinding = null
  }
}

/** 防止 console + 引擎事件 与 unhandledrejection 连续弹多次 */
let lastPackageToastAt = 0
const PACKAGE_TOAST_DEBOUNCE_MS = 2600

function notifyPackageArrearsOnce() {
  const now = Date.now()
  if (now - lastPackageToastAt < PACKAGE_TOAST_DEBOUNCE_MS) return
  lastPackageToastAt = now
  feedback.msgError(getTUICallPackageArrearsMessage())
  showCallKitWindow.value = false
}

/**
 * SDK 在 calls 失败时往往只 console.error（见 CallService），Chat UIKit 工具栏又会吞掉 Promise，
 * 故需：1) 挂钩 console 识别套餐文案；2) 监听 TUICallEngine 的 error 事件。
 */
let savedConsoleError: typeof console.error | null = null
let consoleErrorHooked = false
let engineErrorHandler: ((e: unknown) => void) | null = null

function hookConsoleErrorForPackageHint() {
  if (consoleErrorHooked) return
  savedConsoleError = console.error.bind(console)
  consoleErrorHooked = true
  console.error = (...args: unknown[]) => {
    savedConsoleError!(...args)
    if (!visible.value || !isCallReady.value) return
    const text = args
      .map((a) => {
        if (typeof a === 'string') return a
        if (a instanceof Error) return a.message
        try {
          return JSON.stringify(a)
        } catch {
          return String(a)
        }
      })
      .join(' ')
    if (isTUICallPackageAbilityError(text)) notifyPackageArrearsOnce()
  }
}

function unhookConsoleErrorForPackageHint() {
  if (consoleErrorHooked && savedConsoleError) {
    console.error = savedConsoleError
  }
  consoleErrorHooked = false
  savedConsoleError = null
}

function bindEnginePackageErrorListener() {
  try {
    const engine = TUICallKitServer.getTUICallEngineInstance?.()
    if (!engine?.on) return
    if (engineErrorHandler) {
      engine.off?.('error', engineErrorHandler)
    }
    engineErrorHandler = (payload: unknown) => {
      if (!visible.value || !isCallReady.value) return
      if (isTUICallPackageAbilityError(payload)) notifyPackageArrearsOnce()
    }
    engine.on('error', engineErrorHandler)
  } catch (e) {
    console.warn('绑定 TUICallEngine error 监听失败:', e)
  }
}

function unbindEnginePackageErrorListener() {
  try {
    const engine = TUICallKitServer.getTUICallEngineInstance?.()
    if (engine?.off && engineErrorHandler) {
      engine.off('error', engineErrorHandler)
    }
  } catch {
    //
  }
  engineErrorHandler = null
}

/** 医生端进入会话后发给患者的信令：患者端不展示气泡，仅用于更新「已进入诊室」状态 */
async function sendDoctorEnteredConsultRoomSignal(sdkAppId: number, toUserId: string) {
  try {
    const chat = TencentCloudChat.create({ SDKAppID: sdkAppId })
    const payload = {
      businessID: 'doctor_entered_consult_room',
      time: Date.now()
    }
    const message = chat.createCustomMessage({
      to: toUserId,
      conversationType: TencentCloudChat.TYPES.CONV_C2C,
      payload: {
        data: JSON.stringify(payload)
      }
    })
    await chat.sendMessage(message)
    console.log('已发送医生进入诊室信令（患者端聊天列表不展示）')
  } catch (e) {
    console.warn('发送医生进入诊室信令失败:', e)
  }
}

/** 未捕获 Promise（若存在） */
const onTuiCallUnhandledRejection = (e: PromiseRejectionEvent) => {
  if (!visible.value || !isCallReady.value) return
  if (isTUICallPackageAbilityError(e.reason)) notifyPackageArrearsOnce()
}

onMounted(() => {
  hookConsoleErrorForPackageHint()
  window.addEventListener('unhandledrejection', onTuiCallUnhandledRejection)
})

onUnmounted(() => {
  clearLocalRecordingStartTimer()
  localCallRecorder.reset()
  tearDownCallRoomBinding?.()
  unbindEngineRoomEvents()
  unbindImHangupMessageListener()
  unhookConsoleErrorForPackageHint()
  unbindEnginePackageErrorListener()
  window.removeEventListener('unhandledrejection', onTuiCallUnhandledRejection)
})

// 更新用户头像到 IM
const updateUserAvatarToIM = async (avatarUrl: string,appid: number) => {
  try {
    if (!avatarUrl) {
      console.warn('头像 URL 为空，跳过更新')
      return
    }
    
    console.log('开始更新头像到 IM:', avatarUrl)
    
    // 尝试通过 SDK 实例更新头像
    // 在 UIKit 登录后，SDK 实例应该已初始化
    const updateResult = await new Promise((resolve, reject) => {
      // 延迟执行，确保 SDK 完全初始化
      setTimeout(async () => {
        try {
          // 尝试多种方式获取 SDK 实例
       
          let chat = TencentCloudChat.create( { SDKAppID: appid });
          // 调用 updateMyProfile 更新用户头像
          const response = await chat.updateMyProfile({
            avatar: avatarUrl
          })
          
          console.log('IM 头像更新成功:', response)
          resolve(response)
        } catch (err) {
          console.error('更新头像异常:', err)
          resolve(null)
        }
      }, 500)
    })
    
  } catch (err: any) {
    console.error('IM 头像更新失败:', err.message || err)
    // 不中断主流程，头像更新失败不影响聊天功能
  }
}

// 监听当前激活的会话变化，更新标题
watch(() => activeConversation.value, async (newConversation) => {
  if (newConversation) {
    // 更新标题为当前会话的显示名称
    patientName.value = newConversation.getShowName() || patientName.value
    console.log('会话切换:', newConversation.conversationID, '名称:', patientName.value, newConversation)
    
    if (newConversation.userProfile.userID.includes("doctor")) {
      console.log('当前会话是医生，跳过获取 assistant_id')
    } else {
      try {
        const res = await getCallSignature({ 
          patient_id: newConversation.userProfile.userID.replace("patient_", ""),
          diagnosis_id: diagnosisId.value
        })
        syncLochostVodFromSignature(res)
        patientUserId.value = res.patientUserId
        assistant_ids.value = res.assistant_id
        console.log('会话切换后获取到 assistant_id:', assistant_ids.value, '完整响应:', res)
      } catch (err) {
        console.error('获取 assistant_id 失败:', err)
      }
    }
    
    console.log('会话切换完成，userID:', newConversation.userProfile.userID)
  }
})

const open = async (data: { patientId: number; patientName: string; diagnosisId?: number }) => {
  visible.value = true
  isMinimized.value = false
  posX.value = 100
  posY.value = 80
  resetCallKitPositionToLeft()
  patientName.value = data.patientName
  patientId.value = data.patientId
  diagnosisId.value = data.diagnosisId || null
  error.value = ''
  isReady.value = false
  isCallReady.value = false
  showCallKitWindow.value = false
  loadingText.value = '正在获取签名...'
 
  try {
    // 获取医生的签名信息
    const res = await getCallSignature({ 
      patient_id: data.patientId,
      diagnosis_id: data.diagnosisId || 0
    })
    syncLochostVodFromSignature(res)
    assistant_ids.value = res.assistant_id
    console.log('后端返回的签名数据:', res)
    console.log('assistant_id:', assistant_ids.value)
    
    if (!res || !res.userSig) {
      throw new Error('获取签名失败')
    }
    
    if (!res.assistant_id) {
      console.warn('警告：后端未返回 assistant_id，群视频通话可能无法正常工作')
    }
    
    patientUserId.value = res.patientUserId || `patient_${data.patientId}`
    
    loadingText.value = '正在登录聊天...'
    
    // 登录 Chat UIKit
    await login({
      sdkAppId: Number(res.sdkAppId),
      userId: res.userId,
      userSig: res.userSig,
      useUploadPlugin: true
    })
    // IM 自定义信令：对端挂断等 → 立即 endCall 停合流（不依赖小程序上报）
    bindImHangupMessageListener()
    const sdkAPPid = Number(res.sdkAppId)

    // 初始化 TUICallKit（聊天内音视频通话依赖此初始化）；只需全局成功一次
    if (!(window as any).__TUICallKitInited__) {
      loadingText.value = '正在初始化通话能力...'
      try {
        await TUICallKitServer.init({
          userID: res.userId,
          userSig: res.userSig,
          SDKAppID: sdkAPPid
        })
        ;(window as any).__TUICallKitInited__ = true
      } catch (err: any) {
        console.warn('TUICallKit 初始化失败，聊天仍可用，音视频通话不可用:', err?.message || err)
        if (isTUICallPackageAbilityError(err)) {
          notifyPackageArrearsOnce()
        }
      }
    }
    isCallReady.value = !!(window as any).__TUICallKitInited__
    if (isCallReady.value) {
      // 仅在通话时显示视频窗口；发起通话前落库、接通后绑定房间号（自动云端录制 + 浏览器本地录制）
      TUICallKitServer.setCallback({
        beforeCalling: () => {
          installTrtcCloudPreStopHook()
          clearLocalRecordingStartTimer()
          localCallRecorder.reset()
          localRecordingBadge.value = false
          localRecordingRound++
          recordingDiagnosisIdSnapshot = diagnosisId.value
          resetCallKitPositionToLeft()
          showCallKitWindow.value = true
          pendingCallRecordStart = ensureCallRecordStarted()
          void pendingCallRecordStart
        },
        // 须先 endCall（DeleteCloudRecording），再 await 本地上传；否则上传 WebM 耗时数分钟会拖住云端停录，控制台房间长期「尚未结束」
        afterCalling: () => {
          localCallRecorder.beginStopNow()
          void (async () => {
            await reportCallEnded()
            showCallKitWindow.value = false
            await finalizeLocalRecordingAndAttach()
          })()
        }
      })
      installTUICallKitRoomHooks()
      installEngineRoomCapture()
      installHangupPreStopHook()
      installTrtcCloudPreStopHook()
      bindEngineRoomEvents()
      setupCallRoomBinding()
      bindEnginePackageErrorListener()
    }
    console.log('Chat UIKit 登录成功，患者用户ID:', patientUserId.value)
    
    loadingText.value = '加载聊天界面...'
    
    // 等待 UIKit 初始化完成后创建并激活会话
    setTimeout(async () => {
      isReady.value = true
      
      // 使用 nextTick 确保组件已渲染
      await nextTick()
      
      // 增加延迟，确保 TIM SDK 完全初始化
      await new Promise(resolve => setTimeout(resolve, 1000))
      
      // 更新用户头像到 IM - 从 Pinia store 获取当前登录用户的头像
      const avatar = userStore.userInfo?.avatar
      console.log('准备更新头像，avatar:', avatar)
      
      if (avatar) {
        await updateUserAvatarToIM(avatar,sdkAPPid)
      }
      
      // 创建或获取与患者的单聊会话
      try {
        const conversation = await createC2CConversation(patientUserId.value)
        conversationId.value = conversation.conversationID
        console.log('已创建/获取会话:', conversationId.value)
        
        // 激活该会话
        setActiveConversation(conversationId.value)
        console.log('已激活会话')

        await sendDoctorEnteredConsultRoomSignal(sdkAPPid, patientUserId.value)
      } catch (err) {
        console.error('创建/激活会话失败:', err)
      }
    }, 800)
    
  } catch (err: any) {
    console.error('初始化聊天失败:', err)
    error.value = err.message || '初始化聊天失败'
    feedback.msgError(error.value)
  }
}

// 群组视频通话（多人通话）：当前医生作为发起方，默认先邀请当前会话患者
const startGroupVideoCall = async () => {
  if (!isCallReady.value) {
    feedback.msgWarning('通话服务初始化中，请稍后再试')
    return
  }

  if (!patientUserId.value) {
    feedback.msgWarning('未找到患者通话ID，无法发起群视频')
    return
  }

  try {
    // 确保 assistant_id 存在
    if (!assistant_ids.value) {
      console.warn('assistant_id 为空，尝试重新获取')
      const res = await getCallSignature({ 
        patient_id: patientId.value!,
        diagnosis_id: diagnosisId.value
      })
      syncLochostVodFromSignature(res)
      assistant_ids.value = res.assistant_id
      console.log('重新获取 assistant_id:', assistant_ids.value)
    }

    // 构建用户列表，过滤掉空值
    const userIDList = [patientUserId.value, assistant_ids.value].filter(id => id)
    
    if (userIDList.length < 2) {
      feedback.msgWarning('助理ID获取失败，无法发起群视频')
      console.error('userIDList 不完整:', userIDList, 'assistant_ids:', assistant_ids.value)
      return
    }
    
    console.log('发起群视频通话，参与者:', userIDList)

    await TUICallKitServer.calls({
      userIDList,
      type: TUICallType.VIDEO_CALL
    })
  } catch (err: any) {
    console.error('发起群视频失败:', err)
    if (isTUICallPackageAbilityError(err)) {
      notifyPackageArrearsOnce()
    } else {
      feedback.msgError(formatTUICallUserError(err, '发起群视频失败'))
    }
  }
}

// 浮动窗口位置与拖动
const chatWindowRef = ref<HTMLElement>()
const posX = ref(100)
const posY = ref(80)
const isDragging = ref(false)
const dragStartX = ref(0)
const dragStartY = ref(0)
const posStartX = ref(0)
const posStartY = ref(0)

// TUICallKit 视频窗口拖动
const callKitWrapperRef = ref<HTMLElement>()
const callKitContentRef = ref<HTMLElement>()
const captureUploading = ref(false)
/** 与 .chat-dialog-call-kit-wrapper 尺寸一致 */
const CALL_KIT_FLOAT_W = 375
const CALL_KIT_FLOAT_H = 667
const CALL_KIT_EDGE = 16

const callKitX = ref(0)
const callKitY = ref(0)
/** 视频浮窗默认靠左、垂直居中（每次发起通话也会重置，避免沿用上次拖动位置） */
function resetCallKitPositionToLeft() {
  callKitX.value = CALL_KIT_EDGE
  callKitY.value = Math.max(
    CALL_KIT_EDGE,
    Math.floor((window.innerHeight - CALL_KIT_FLOAT_H) / 2)
  )
}

const isCallKitDragging = ref(false)
const callKitDragStartX = ref(0)
const callKitDragStartY = ref(0)
const callKitPosStartX = ref(0)
const callKitPosStartY = ref(0)

const callKitStyle = computed(() => ({
  left: `${callKitX.value}px`,
  top: `${callKitY.value}px`
}))

const onCallKitDragStart = (e: MouseEvent) => {
  isCallKitDragging.value = true
  callKitDragStartX.value = e.clientX
  callKitDragStartY.value = e.clientY
  callKitPosStartX.value = callKitX.value
  callKitPosStartY.value = callKitY.value
  document.addEventListener('mousemove', onCallKitDragMove)
  document.addEventListener('mouseup', onCallKitDragEnd)
}

const onCallKitDragMove = (e: MouseEvent) => {
  if (!isCallKitDragging.value) return
  callKitX.value = Math.max(0, callKitPosStartX.value + e.clientX - callKitDragStartX.value)
  callKitY.value = Math.max(0, callKitPosStartY.value + e.clientY - callKitDragStartY.value)
}

const onCallKitDragEnd = () => {
  isCallKitDragging.value = false
  document.removeEventListener('mousemove', onCallKitDragMove)
  document.removeEventListener('mouseup', onCallKitDragEnd)
}

/** 截取当前视频画面并上传，同步到对应患者诊单的「舌苔照片」 */
const handleCallKitScreenshot = async () => {
  // 同步防重入：避免连点触发两次上传、诊单里出现两张图
  if (captureUploading.value) return
  captureUploading.value = true
  try {
    if (patientId.value == null) {
      feedback.msgWarning('缺少患者信息，无法同步舌苔照片')
      return
    }
    const wrap = callKitWrapperRef.value
    const content = callKitContentRef.value
    const root = wrap || content
    if (!root) return
    const blob = await captureVideoFrameFromElement(root, {
      extraRoots: [content].filter(Boolean) as HTMLElement[]
    })
    if (!blob) {
      feedback.msgWarning('截屏失败，请确认视频画面已加载')
      return
    }
    const data = await uploadImageBlob(blob, `callshot-${Date.now()}.jpg`)
    const path = data.url || data.uri
    if (!path) {
      feedback.msgError('上传未返回图片路径')
      return
    }
    if (diagnosisId.value == null) {
      feedback.msgWarning('请先保存诊单后再从视频同步舌苔照片')
      return
    }
    const detail = await tcmDiagnosisDetail({ id: diagnosisId.value })
    if (Number(detail.patient_id) !== Number(patientId.value)) {
      feedback.msgError('患者与当前诊单不匹配，无法同步')
      return
    }
    const previous: string[] = [...((detail.tongue_images || []) as string[])]
    if (previous.length >= 9) {
      feedback.msgWarning('舌苔照片已达上限 9 张')
      return
    }
    const next = [...previous, path]
    const payload = JSON.parse(JSON.stringify(detail)) as Record<string, unknown>
    payload.tongue_images = next
    await tcmDiagnosisEdit(payload)
    feedback.msgSuccess('舌苔照片已同步到诊单')
    window.dispatchEvent(
      new CustomEvent<DiagnosisTongueImagesUpdatedDetail>(DIAGNOSIS_TONGUE_IMAGES_UPDATED, {
        detail: {
          diagnosisId: diagnosisId.value,
          tongue_images: next
        }
      })
    )
    try {
      await addDoctorNote({ diagnosis_id: diagnosisId.value, tongue_images: [path] })
    } catch { /* 写入备注表失败不阻塞主流程 */ }
  } catch (e: unknown) {
    const err = e as Error
    console.error(err)
    feedback.msgError(err?.message || '上传失败')
  } finally {
    captureUploading.value = false
  }
}

/** 关闭视频浮窗并挂断当前通话 */
const handleCallKitClose = async () => {
  if (isCallReady.value) {
    try {
      localCallRecorder.beginStopNow()
      await finalizeLocalRecordingAndAttach()
      await TUICallKitServer.hangup()
    } catch (err) {
      console.warn('关闭视频窗口时挂断失败:', err)
    }
  }
  showCallKitWindow.value = false
}

const windowStyle = computed(() => ({
  left: `${posX.value}px`,
  top: `${posY.value}px`
}))

const onHeaderMouseDown = (e: MouseEvent) => {
  const t = e.target as HTMLElement
  if (t.closest('.chat-close-btn') || t.closest('.chat-header-actions')) return
  isDragging.value = true
  dragStartX.value = e.clientX
  dragStartY.value = e.clientY
  posStartX.value = posX.value
  posStartY.value = posY.value
  document.addEventListener('mousemove', onHeaderMouseMove)
  document.addEventListener('mouseup', onHeaderMouseUp)
}

const onHeaderMouseMove = (e: MouseEvent) => {
  if (!isDragging.value) return
  posX.value = Math.max(0, posStartX.value + e.clientX - dragStartX.value)
  posY.value = Math.max(0, posStartY.value + e.clientY - dragStartY.value)
}

const onHeaderMouseUp = () => {
  isDragging.value = false
  document.removeEventListener('mousemove', onHeaderMouseMove)
  document.removeEventListener('mouseup', onHeaderMouseUp)
}

const handleClose = async () => {
  unbindImHangupMessageListener()
  // 关闭时如有通话中/拨通中，先结束本地录制再挂断（不依赖悬浮窗是否显示）
  if (isCallReady.value) {
    const st = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS)
    if (st === CALL_STATUS_CALLING || st === CALL_STATUS_CONNECTED) {
      try {
        localCallRecorder.beginStopNow()
        await finalizeLocalRecordingAndAttach()
        await TUICallKitServer.hangup()
      } catch (err) {
        console.warn('挂断通话时出错:', err)
      }
    }
  }
  if (diagnosisId.value != null) {
    await reportCallEnded()
  }
  try {
    // 登出 Chat
   // await logout()
  } catch (err) {
    console.error('登出失败:', err)
  }
  
  visible.value = false
  showCallKitWindow.value = false
  // isReady.value = false
  // error.value = ''
  // patientId.value = null
  // diagnosisId.value = null
  // patientUserId.value = ''
  // conversationId.value = ''
}

defineExpose({ open })
</script>

<style scoped lang="scss">
/* 浮动窗口：无遮罩，不遮挡页面，可任意拖动 */
.chat-floating-window {
  position: fixed;
  width: 600px;
  height: 75vh;
  min-height: 400px;
  max-height: 90vh;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
  z-index: 2501;
  display: flex;
  flex-direction: column;
  overflow: hidden;

  &--minimized {
    width: min(360px, 92vw);
    height: auto;
    min-height: 0;
    max-height: none;
    box-shadow: 0 2px 16px rgba(0, 0, 0, 0.12);
  }
}

.chat-window-header {
  padding: 16px 20px;
  border-bottom: 1px solid #e4e7ed;
  background: #fff;
  cursor: move;
  user-select: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;

  .chat-window-title {
    font-size: 16px;
    font-weight: 500;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-right: 8px;
  }

  .chat-header-actions {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-shrink: 0;
  }

  .chat-header-btn-text {
    margin-left: 2px;
    font-size: 13px;
  }

  .chat-close-btn,
  .chat-minimize-btn,
  .chat-expand-btn {
    cursor: pointer;
    flex-shrink: 0;
  }
}

.chat-container {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  color: #909399;
  
  .el-icon {
    font-size: 32px;
  }
}

.error-container {
  width: 100%;
  padding: 20px;
}

.chat-content {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}

.chat-layout {
  display: flex;
  height: 100%;
  width: 100%;
  
  .chat-area {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    position: relative;
  }
  
  .empty-chat {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

/* 消息工具栏样式 */
.message-toolbar {
  display: flex;
  gap: 8px;
  align-items: center;
}

/* TUICallKit 可拖动容器 - 手机尺寸 (375x667) */
.chat-dialog-call-kit-wrapper {
  position: fixed;
  width: 375px;
  height: 667px;
  max-width: 90vw;
  z-index: 300000;
  background: #1a1a1a;
  border-radius: 24px;
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
}

.call-kit-drag-handle {
  height: 32px;
  padding: 0 12px;
  background: #2a2a2a;
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: move;
  user-select: none;
  flex-shrink: 0;

  .call-kit-drag-handle-left {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
  }

  .drag-handle-text {
    font-size: 13px;
    color: #fff;
  }

  .call-local-recording-hint {
    font-size: 11px;
    color: #f89898;
    white-space: nowrap;
  }

  .call-kit-drag-handle-actions {
    display: flex;
    align-items: center;
    gap: 4px;
  }

  .call-kit-screenshot-btn {
    padding: 4px 6px;
    color: #79bbff;

    &:hover {
      color: #a0cfff;
    }
  }

  .call-kit-screenshot-text {
    margin-left: 2px;
    font-size: 12px;
  }

  .call-kit-close-btn {
    cursor: pointer;
    padding: 4px;
    color: #f56c6c;

    &:hover {
      color: #f89898;
    }
  }

  .drag-handle-icon {
    color: #999;
    font-size: 16px;
  }
}

.call-kit-content {
  flex: 1;
  min-height: 0;
  position: relative;
}

.chat-dialog-call-kit {
  position: absolute !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100% !important;
  height: 100% !important;
  transform: none !important;
  max-width: none !important;
  max-height: none !important;
  border-radius: 0 0 24px 24px;
}
</style>

<style scoped lang="scss">
/* 全局样式：确保表情选择器等弹出层有足够高的 z-index */
.el-popper,
.el-picker-panel,
[data-reka-popper],
[data-reka-popper-content-wrapper],
[class*="emoji-picker"],
[class*="picker-popup"],
[class*="tui-picker"],
body > div[id^="el-popper-container"],
body > div[data-reka-focus-guard] {
  z-index: 9999 !important;
}

/* 确保聊天窗口内的弹出层也能正确显示 */
.chat-floating-window [data-reka-popper-content-wrapper] {
  z-index: 9999 !important;
}

</style>
