<template>
  <div class="reader-container">
    <!-- 顶部工具栏 -->
    <div class="reader-header">
      <div class="header-left">
        <el-button link type="primary" @click="goBack" icon="ArrowLeft">返回</el-button>
        <span class="role-badge" :class="role === 'customer' ? 'customer' : 'consultant'">
          {{ role === 'customer' ? '👤 客户' : '👤 顾问' }}
        </span>
        <span v-if="presenter !== 'none'" class="presenter-badge">
          📺 {{ presenter === role ? '您正在投屏' : '对方投屏中' }}
        </span>
        <span v-if="remoteControlActive" class="remote-badge">
          🖱️ {{ remoteControlDirection === 'controlling' ? '正在控制对方' : '正在被控制' }}
        </span>
      </div>
      <div class="header-center">
        <span class="title">{{ currentArticle?.title || '加载中...' }}</span>
        <span class="page-info">第 {{ currentPage + 1 }} / {{ articles.length }} 篇</span>
      </div>
      <div class="header-right">
        <el-badge :value="recordingStatus === 'recording' ? '录制中' : recordingStatus === 'stopped' ? '已录制' : '未录制'"
                  :type="recordingStatus === 'recording' ? 'danger' : recordingStatus === 'stopped' ? 'success' : 'info'">
          <el-button link type="primary" icon="VideoCamera" @click="toggleRecording" />
        </el-badge>
        <el-badge :value="wsConnected ? '在线' : '离线'" :type="wsConnected ? 'success' : 'danger'">
          <el-button link type="primary" icon="Connection" />
        </el-badge>
        <el-button type="primary" size="small" @click="testRemoteClick">
          🖱️ 测试点击
        </el-button>
      </div>
    </div>

    <!-- 内容区域 -->
    <div class="reader-body" ref="readerBodyRef">
      <div v-if="isReceivingScreen" class="video-container">
        <video ref="remoteVideoRef" autoplay playsinline></video>
        <!-- 控制条仅对投屏者显示 -->
        <div class="controls-overlay" v-if="presenter === role">
          <div class="nav-controls">
            <button class="nav-btn" :class="{ disabled: !canOperate }" @click="prevPage" :disabled="!canOperate">◀</button>
            <span class="page-text">{{ currentPage + 1 }} / {{ articles.length }}</span>
            <button class="nav-btn" :class="{ disabled: !canOperate }" @click="nextPage" :disabled="!canOperate">▶</button>
          </div>
          <div class="zoom-controls-overlay">
            <button class="zoom-btn" @click="zoomOut" :disabled="!canOperate">−</button>
            <span>{{ Math.round(zoomLevel * 100) }}%</span>
            <button class="zoom-btn" @click="zoomIn" :disabled="!canOperate">+</button>
          </div>
          <div class="status-tip" v-if="!canOperate">🔒 {{ role === 'customer' ? '顾问' : '客户' }}正在操作</div>
        </div>
        <!-- 远程控制按钮 -->
        <div class="video-actions">
          <el-button v-if="!remoteControlActive && presenter !== roleMap[role]" size="small" type="warning" @click="requestRemoteControl">
            请求远程控制
          </el-button>
          <el-button v-if="remoteControlActive && remoteControlDirection === 'being_controlled'" size="small" type="danger" @click="stopRemoteControl">
            中断控制
          </el-button>
        </div>
      </div>
      <div v-else class="article-wrapper" :style="{ transform: `scale(${zoomLevel})`, transformOrigin: 'top left' }">
        <div class="article-content" ref="articleContentRef" v-html="currentArticle?.content || ''"></div>
      </div>
    </div>

    <div class="reader-footer" v-if="!isReceivingScreen">
      <el-button type="primary" plain circle icon="ArrowLeft" @click="prevPage" :disabled="!canOperate" />
      <el-slider v-model="currentPage" :min="0" :max="articles.length - 1" :show-tooltip="false" style="flex:1; margin:0 16px;" @change="onSliderChange" :disabled="!canOperate" />
      <el-button type="primary" plain circle icon="ArrowRight" @click="nextPage" :disabled="!canOperate" />
    </div>

    <div class="zoom-controls" v-if="!isReceivingScreen">
      <el-button type="primary" plain circle size="small" @click="zoomOut" :disabled="!canOperate || zoomLevel <= 0.5">−</el-button>
      <span class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</span>
      <el-button type="primary" plain circle size="small" @click="zoomIn" :disabled="!canOperate || zoomLevel >= 2.0">+</el-button>
    </div>

    <!-- 悬浮按钮组 -->
    <div class="fab-group">
      <!-- 客户按钮组 -->
      <template v-if="role === 'customer'">
        <!-- 发起/结束协同 -->
        <el-badge :value="sharingStatus === '讲解中' ? '已创建' : '未开始'" :type="sharingStatus === '讲解中' ? 'success' : 'info'">
          <el-button
              v-if="sharingStatus !== '讲解中'"
              class="fab-btn fab-share"
              type="primary"
              circle
              size="large"
              @click="startCoSession"
          >
            <span class="fab-btn-text">发起<br>协同</span>
          </el-button>
          <el-button
              v-else
              class="fab-btn fab-end"
              type="danger"
              circle
              size="large"
              @click="handleEndCoSession"
          >
            <span class="fab-btn-text">结束<br>协同</span>
          </el-button>
        </el-badge>

        <!-- 投屏 -->
        <el-badge v-if="sharingStatus === '讲解中'"
                  :value="presenter === 'customer' ? '您正在投屏' : presenter !== 'none' ? '对方投屏中' : '未投屏'"
                  :type="presenter === 'customer' ? 'danger' : presenter !== 'none' ? 'warning' : 'info'">
          <el-button
              class="fab-btn"
              :class="presenter === 'customer' ? 'fab-end' : 'fab-share'"
              type="primary"
              circle
              size="large"
              @click="toggleScreenShare"
              :disabled="presenter !== 'none' && presenter !== 'customer'"
          >
            <span class="fab-btn-text">{{ presenter === 'customer' ? '结束\n投屏' : '投屏' }}</span>
          </el-button>
        </el-badge>

        <!-- 脱敏 -->
        <el-badge :value="desensitizationEnabled ? '已脱敏' : '未脱敏'" :type="desensitizationEnabled ? 'success' : 'info'">
          <el-button class="fab-btn fab-desensitize" type="primary" circle size="large" @click="toggleDesensitization">
            <span class="fab-btn-text">脱敏</span>
          </el-button>
        </el-badge>
      </template>

      <!-- 顾问按钮组 -->
      <template v-if="role === 'consultant'">
        <!-- 加入 -->
        <el-badge :value="sharingStatus === '讲解中' ? '已加入' : '未加入'" :type="sharingStatus === '讲解中' ? 'success' : 'info'">
          <el-button
              v-if="sharingStatus !== '讲解中'"
              class="fab-btn fab-join"
              type="primary"
              circle
              size="large"
              @click="joinDialogVisible = true"
          >
            <span class="fab-btn-text">加入</span>
          </el-button>
          <!-- 投屏/结束投屏 -->
          <el-button
              v-else
              class="fab-btn"
              :class="presenter === 'consultant' ? 'fab-end' : 'fab-share'"
              type="primary"
              circle
              size="large"
              @click="toggleScreenShare"
              :disabled="presenter !== 'none' && presenter !== 'consultant'"
          >
            <span class="fab-btn-text">{{ presenter === 'consultant' ? '结束\n投屏' : '投屏' }}</span>
          </el-button>
        </el-badge>
        <!-- 顾问投屏 badge 提示（复用上方按钮自带的 badge，此处不需要额外加） -->
      </template>
    </div>

    <!-- 弹窗：共享信息 -->
    <el-dialog title="大屏讲解" v-model="shareDialogVisible" width="400px" append-to-body>
      <div style="text-align:center;padding:16px 0;">
        <el-alert title="请将以下信息分享给顾问" type="success" :closable="false" style="margin-bottom:16px;" />
        <div style="margin-bottom:16px;">
          <div style="font-size:14px;color:#666;">共享码</div>
          <div style="font-size:28px;font-weight:bold;letter-spacing:6px;color:#1890ff;">{{ shareInfo.roomPwd || '------' }}</div>
          <el-button size="small" type="primary" plain @click="copyCode" style="margin-top:8px;">复制</el-button>
        </div>
        <div>
          <div style="font-size:14px;color:#666;">房间号</div>
          <div style="font-size:16px;font-weight:500;color:#333;">{{ shareInfo.roomId || '------' }}</div>
        </div>
      </div>
    </el-dialog>

    <!-- 弹窗：顾问加入 -->
    <el-dialog title="加入协同" v-model="joinDialogVisible" width="400px" append-to-body>
      <el-form :model="joinForm" label-width="80px">
        <el-form-item label="共享码"><el-input v-model="joinForm.roomPwd" placeholder="请输入6位共享码" maxlength="6" /></el-form-item>
        <el-form-item label="房间号"><el-input v-model="joinForm.roomId" placeholder="请输入房间号" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button type="primary" @click="submitJoin">加入</el-button>
        <el-button @click="joinDialogVisible = false">取消</el-button>
      </template>
    </el-dialog>

    <!-- 投屏邀请弹窗 -->
    <el-dialog title="投屏邀请" v-model="inviteDialogVisible" width="400px" append-to-body>
      <p>{{ inviteMessage }}</p>
      <template #footer>
        <el-button @click="respondInvite(false)">拒绝</el-button>
        <el-button type="primary" @click="respondInvite(true)">接受</el-button>
      </template>
    </el-dialog>

    <!-- 远程控制请求弹窗 -->
    <el-dialog title="远程控制请求" v-model="remoteControlInviteVisible" width="400px" append-to-body>
      <p>{{ remoteControlInviteMessage }}</p>
      <template #footer>
        <el-button @click="respondRemoteControl(false)">拒绝</el-button>
        <el-button type="primary" @click="respondRemoteControl(true)">接受</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount, nextTick, getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
  createCoSession,
  endCoSession as apiEndCoSession,
  joinCoSession,
  startRecording,
  stopRecording,
  queryRecording,
  getDesensitizationRules,
  uploadChunk
} from '@/api/system/report'
import useUserStore from '@/store/modules/user'
import { getToken } from '@/utils/auth'

console.log('【共享报告阅读器】组件加载')
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
const role = ref(route.query.role || 'customer')
const roleMap = { customer: 'customer', consultant: 'consultant' }

// ========== 文章数据 ==========
const articles = reactive([
  {
    title: '2026年宏观经济展望',
    content: `
      <h2>2026年宏观经济展望</h2>
      <p>2026年全球经济呈现复苏态势，主要经济体政策趋于宽松。国际货币基金组织（IMF）预测全球GDP增长3.2%，其中新兴市场表现亮眼。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">张三丰</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">13812345678</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E📈 经济走势图%3C/text%3E%3C/svg%3E" alt="经济走势" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>在通胀压力缓解的背景下，美联储预计将于2026年三季度启动降息周期。欧洲央行紧随其后，维持宽松立场。亚洲地区，中国和印度继续成为全球增长引擎。</p>
      <p>值得注意的是，地缘政治风险依然存在，能源价格波动可能对复苏构成扰动。企业需关注供应链多元化布局。</p>
    `
  },
  {
    title: '人工智能与产业变革',
    content: `
      <h2>人工智能与产业变革</h2>
      <p>人工智能（AI）正以前所未有的速度渗透各行各业。2026年，生成式AI已广泛应用于内容创作、客户服务和研发设计。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">李思思</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">15987654321</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E🤖 AI应用场景%3C/text%3E%3C/svg%3E" alt="AI应用" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>据Gartner预测，到2027年，70%的企业将部署AI助手。金融行业率先采用AI进行风险评估和智能投顾，效率提升超过40%。</p>
      <p>然而，AI也带来数据隐私和就业结构调整的挑战。监管部门正加紧制定AI伦理框架，确保技术向善。</p>
    `
  },
  {
    title: '绿色能源投资新机遇',
    content: `
      <h2>绿色能源投资新机遇</h2>
      <p>全球碳中和目标驱动绿色能源投资爆发式增长。2026年，可再生能源投资预计达到1.2万亿美元，创历史新高。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">王小明</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">13612349876</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E🌱 新能源投资%3C/text%3E%3C/svg%3E" alt="绿色能源" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>光伏和风电成本持续下降，储能技术突破使可再生能源稳定性大幅提升。氢能成为新的投资热点，多个国家公布氢能战略。</p>
      <p>在政策利好和技术成熟的双轮驱动下，绿色金融产品创新不断，ESG投资理念深入人心。</p>
    `
  },
  {
    title: '消费市场趋势洞察',
    content: `
      <h2>消费市场趋势洞察</h2>
      <p>2026年消费市场呈现"理性务实"和"体验升级"双重特征。Z世代成为消费主力，注重个性化与社交属性。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">赵丽颖</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">13800008888</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E🛍️ 消费趋势%3C/text%3E%3C/svg%3E" alt="消费趋势" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>电商领域，直播带货和社交电商持续增长，而线下零售通过数字化改造实现复苏。国潮品牌崛起，文化自信驱动国货消费。</p>
      <p>健康消费成为新蓝海，功能性食品、智能健身设备需求激增。同时，可持续消费理念影响购买决策。</p>
    `
  },
  {
    title: '数字化转型实战指南',
    content: `
      <h2>数字化转型实战指南</h2>
      <p>数字化转型已从"选修课"变为"必修课"。2026年，企业数字化成熟度成为核心竞争力。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">刘德华</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">18912348888</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E💻 数字化转型%3C/text%3E%3C/svg%3E" alt="数字化" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>成功转型的企业注重顶层设计、数据治理和人才梯队建设。云原生、AI和大数据分析成为核心技术底座。</p>
      <p>本指南提供从战略规划到实施落地的全流程框架，助您把握数字化时代的先机。</p>
    `
  }
])

// ========== 页面状态 ==========
const currentPage = ref(0)
const zoomLevel = ref(1.0)
const scrollRatio = ref(0)
const desensitizationEnabled = ref(false)
const readerBodyRef = ref(null)
const articleContentRef = ref(null)
const originalContentMap = new Map()

// ========== 协同状态 ==========
const sharingStatus = ref('未共享')
const shareDialogVisible = ref(false)
const joinDialogVisible = ref(false)
const wsConnected = ref(false)
const isController = ref(false)
const shareInfo = reactive({ roomId: '', roomPwd: '', sessionBizId: '', ownerId: '' })
const joinForm = reactive({ roomId: '', roomPwd: '' })

// ========== 投屏状态 ==========
const presenter = ref('none')
const isReceivingScreen = ref(false)
const remoteVideoRef = ref(null)

// ========== 远程控制 ==========
const remoteControlActive = ref(false)
const remoteControlDirection = ref(null)
let dataChannel = null
let isDataChannelOpen = false
const remoteControlInviteVisible = ref(false)
const remoteControlInviteMessage = ref('')
let pendingRemoteControl = null
const inviteDialogVisible = ref(false)
const inviteMessage = ref('')
let pendingInvite = null

// ========== WebRTC ==========
let pc = null
let sharedStream = null

// ========== WebSocket ==========
let ws = null
let wsReconnectTimer = null
let shouldReconnect = true

// ========== 录制相关 ==========
const recordingStatus = ref('idle')
const recordingTaskId = ref(null)
const mediaRecorder = ref(null)
const uploadedChunkCount = ref(0)
const recordingStartTime = ref(null)
const recordingDuration = ref(0)
let durationTimer = null
const recordedFileUrl = ref('')
const isStopping = ref(false)
let uploadPromises = []

// 项目业务ID（为空时后端自动使用默认OSS）
const projectBizId = ref('')

// ========== 计算属性 ==========
const currentArticle = computed(() => articles[currentPage.value] || articles[0])
const canOperate = computed(() => wsConnected.value && isController.value)

// ========== 页面导航 ==========
function goBack() { router.push('/system/report') }

function prevPage() { if (canOperate.value && currentPage.value > 0) changePage(currentPage.value - 1) }
function nextPage() { if (canOperate.value && currentPage.value < articles.length - 1) changePage(currentPage.value + 1) }
function onSliderChange(val) { if (!canOperate.value) return; changePage(val); nextTick(() => { sendSyncAction('turn_the_page', 0) }) }
function zoomIn() { if (canOperate.value && zoomLevel.value < 2.0) { zoomLevel.value = Math.min(2.0, zoomLevel.value + 0.1); sendSyncAction('scaling', zoomLevel.value) } }
function zoomOut() { if (canOperate.value && zoomLevel.value > 0.5) { zoomLevel.value = Math.max(0.5, zoomLevel.value - 0.1); sendSyncAction('scaling', zoomLevel.value) } }
function changePage(newPage) { if (newPage < 0 || newPage >= articles.length) return; currentPage.value = newPage; if (desensitizationEnabled.value) applyDesensitization(); else restoreOriginalContent() }

// ========== 控制权 ==========
function toggleControl() {
  if (role.value !== 'consultant') { ElMessage.warning('只有顾问可以切换控制权'); return }
  if (!wsConnected.value) { ElMessage.warning('未连接协同'); return }
  const targetHolderType = isController.value ? 'owner' : 'participant'
  isController.value = !isController.value
  ws.send(JSON.stringify({ action: 'control_transfer', holderType: targetHolderType }))
}

// ========== WebSocket 连接 ==========
function connectWebSocket(roomId, userId, userType, sendControl = false, onOpenCallback = null) {
  if (ws) { try { ws.close() } catch (e) {}; ws = null }
  if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
  const wsUrl = `ws://139.224.145.34:9482/communication/api/ws/${roomId}?userId=${userId}&userType=${userType}`
  console.log('【WebSocket】连接地址:', wsUrl)
  ws = new WebSocket(wsUrl)
  ws.onopen = () => {
    wsConnected.value = true
    ElMessage.success('WebSocket 已连接')
    if (onOpenCallback) {
      onOpenCallback()
    }
    if (sendControl && role.value === 'consultant') {
      ws.send(JSON.stringify({ action: 'control_transfer', holderType: 'participant' }))
      setTimeout(() => {
        if (ws && ws.readyState === WebSocket.OPEN) {
          ws.send(JSON.stringify({ action: 'request_offer', roomId }))
        }
      }, 500)
    }
  }
  ws.onmessage = (event) => {
    try { handleWebSocketMessage(JSON.parse(event.data)) } catch (e) { console.error('解析消息失败:', e) }
  }
  ws.onclose = () => {
    wsConnected.value = false
    closeWebRTC()
    if (shouldReconnect) {
      ElMessage.warning('WebSocket 断开，尝试重连...')
      if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
      wsReconnectTimer = setTimeout(() => connectWebSocket(roomId, userId, userType), 3000)
    } else {
      console.log('【WebSocket】主动关闭，不再重连')
      shouldReconnect = true
    }
  }
  ws.onerror = (err) => { console.error('WebSocket 错误:', err); wsConnected.value = false }
}

function connectAsCustomer() {
  if (!shareInfo.roomId) return
  shouldReconnect = true
  connectWebSocket(shareInfo.roomId, userStore.userBizId || 'CUST-001', 'owner')
}

// ========== 顾问加入 ==========
async function submitJoin() {
  if (!joinForm.roomPwd || !joinForm.roomId) { ElMessage.warning('请填写完整信息'); return }
  try {
    const res = await joinCoSession({
      roomId: joinForm.roomId,
      roomPwd: joinForm.roomPwd,
      participantId: userStore.userBizId || 'CONS-001',
      participantType: 'consultant'
    })
    if (res.code === 200) {
      if (res.data?.sessionBizId) shareInfo.sessionBizId = res.data.sessionBizId
      if (res.data?.ownerId) shareInfo.ownerId = res.data.ownerId
      sharingStatus.value = '讲解中'
      ElMessage.success('加入成功')
      joinDialogVisible.value = false
      shouldReconnect = true
      const onOpen = () => {
        if (ws && ws.readyState === WebSocket.OPEN) {
          ws.send(JSON.stringify({ action: 'request_offer', roomId: joinForm.roomId }))
        }
      }
      connectWebSocket(joinForm.roomId, userStore.userBizId || 'CONS-001', 'participant', false, onOpen)
    } else {
      ElMessage.error(res.msg || '加入失败')
    }
  } catch (error) {
    console.error('【join】接口异常:', error)
    ElMessage.error('加入失败，请稍后重试')
  }
}

// ========== 投屏核心逻辑 ==========
async function startCoSession() {
  if (sharingStatus.value === '讲解中') {
    ElMessage.warning('已创建协同会话，请等待顾问加入')
    return
  }
  await createSessionOnly()
}

async function handleEndCoSession() {
  if (recordingStatus.value === 'recording') {
    await stopRecordingAndUpload()
  }
  await stopSharing()
  if (role.value === 'customer') {
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ action: 'end_sharing' }))
    }
    await apiEndCoSession({ roomId: shareInfo.roomId })
    ElMessage.success('共享已结束')
  }
  shouldReconnect = false
  if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
  if (ws) { ws.close(); ws = null }
  wsConnected.value = false
  sharingStatus.value = '已结束'
  closeWebRTC()
  if (sharedStream) {
    sharedStream.getTracks().forEach(track => track.stop())
    sharedStream = null
  }
}

async function toggleScreenShare() {
  if (presenter.value === role.value) {
    await stopSharing()
    return
  }
  if (presenter.value === 'none') {
    await startLocalSharing()
    return
  }
}

async function stopSharing() {
  if (presenter.value === 'none') return
  if (recordingStatus.value === 'recording') {
    await stopRecordingAndUpload()
  }
  stopLocalSharing()
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ action: 'presenter_revoke' }))
  }
  presenter.value = 'none'
  ElMessage.success('已停止投屏')
}

async function createSessionOnly() {
  const userId = userStore.userBizId || 'admin'
  const token = getToken() || 'demo-token'
  try {
    const res = await createCoSession({
      scope: 'single',
      resourceType: 'report',
      resourceId: 'RPT-DEMO',
      resourceInit: JSON.stringify({
        url: window.location.href,
        title: currentArticle.value.title,
        page_type: 'article',
        data: {
          currentPage: currentPage.value,
          totalPages: articles.length,
          scrollRatio: scrollRatio.value,
          zoomLevel: zoomLevel.value,
          desensitizationEnabled: desensitizationEnabled.value,
          pageTitle: currentArticle.value.title,
          chartIndices: []
        }
      }),
      ownerId: userId,
      ownerType: 'customer',
      userId: userId,
      token: token
    })
    if (res.code === 200) {
      const data = res.data
      shareInfo.roomId = data.roomId
      shareInfo.roomPwd = data.roomPwd
      shareInfo.sessionBizId = data.sessionBizId
      shareInfo.ownerId = userId
      sharingStatus.value = '讲解中'
      shareDialogVisible.value = true
      ElMessage.success('协同会话已创建，请分享共享码给顾问')
      shouldReconnect = true
      connectWebSocket(shareInfo.roomId, userId, 'owner')
    } else {
      ElMessage.error(res.msg || '创建失败')
    }
  } catch (e) {
    shareInfo.roomId = 'room_' + Math.random().toString(36).substr(2, 8)
    shareInfo.roomPwd = Math.random().toString().slice(2, 8)
    shareInfo.sessionBizId = 'sess_' + Date.now()
    shareInfo.ownerId = userId
    sharingStatus.value = '讲解中'
    shareDialogVisible.value = true
    ElMessage.success('（模拟）协同会话已创建')
    shouldReconnect = true
    connectWebSocket(shareInfo.roomId, userId, 'owner')
  }
}

async function startLocalSharing() {
  if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
    ElMessage.error('浏览器不支持屏幕共享')
    return
  }
  try {
    if (pc) { pc.close(); pc = null }
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: { frameRate: { ideal: 30 }, cursor: 'always' },
      audio: true
    })
    sharedStream = stream

    if (shareInfo.sessionBizId) {
      await startRecordingWithStream(stream)
    }

    pc = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' }
      ]
    })
    stream.getTracks().forEach(track => pc.addTrack(track, stream))
    pc.onicecandidate = (event) => {
      if (event.candidate) {
        ws.send(JSON.stringify({ action: 'webrtc_ice_candidate', roomId: shareInfo.roomId, candidate: event.candidate }))
      }
    }
    pc.onconnectionstatechange = () => {
      if (pc.connectionState === 'connected') ElMessage.success('屏幕共享已连接')
    }
    setupDataChannelListener()

    const offer = await pc.createOffer()
    await pc.setLocalDescription(offer)
    ws.send(JSON.stringify({ action: 'webrtc_offer', roomId: shareInfo.roomId, sdp: offer }))
    presenter.value = role.value === 'customer' ? 'customer' : 'consultant'
    ws.send(JSON.stringify({ action: 'presenter_change', target: presenter.value, from: roleMap[role] }))
  } catch (error) {
    console.error('启动投屏失败:', error)
    ElMessage.error('启动投屏失败: ' + error.message)
  }
}

function stopLocalSharing() {
  if (pc) { pc.close(); pc = null }
  if (sharedStream) {
    sharedStream.getTracks().forEach(track => track.stop())
    sharedStream = null
  }
  isReceivingScreen.value = false
  if (remoteVideoRef.value) {
    remoteVideoRef.value.srcObject = null
  }
}

// ========== 投屏切换处理 ==========
function handlePresenterChange(msg) {
  if (msg.from === roleMap[role]) {
    presenter.value = msg.from
    return
  }
  if (msg.target) {
    presenter.value = msg.target
    return
  }
  if (msg.target === roleMap[role]) {
    inviteMessage.value = `${msg.from === 'customer' ? '客户' : '顾问'} 请求投屏，是否接受？`
    pendingInvite = msg
    inviteDialogVisible.value = true
  } else {
    ws.send(JSON.stringify({ action: 'presenter_response', accept: false, target: msg.target }))
  }
}

function respondInvite(accept) {
  inviteDialogVisible.value = false
  ws.send(JSON.stringify({ action: 'presenter_response', accept, target: pendingInvite.target }))
  if (accept) {
    stopLocalSharing()
    startLocalSharing()
  }
}

function handlePresenterResponse(msg) {
  if (msg.accept) {
    stopLocalSharing()
    presenter.value = msg.target
    ElMessage.success('投屏已切换')
  } else {
    ElMessage.info('对方拒绝了投屏切换')
  }
}

// ========== 远程控制 ==========
function requestRemoteControl() {
  if (!wsConnected.value) {
    ElMessage.warning('未连接协同')
    return
  }
  if (remoteControlActive.value) {
    ElMessage.warning('远程控制已激活')
    return
  }
  if (presenter.value === 'none') {
    ElMessage.warning('当前无人投屏，无法请求远程控制')
    return
  }
  ws.send(JSON.stringify({
    action: 'request_remote_control',
    target: presenter.value,
    from: role.value === 'customer' ? 'customer' : 'consultant'
  }))
  ElMessage.info('已发送远程控制请求，等待对方接受...')
}

function respondRemoteControl(accept) {
  remoteControlInviteVisible.value = false
  ws.send(JSON.stringify({
    action: 'remote_control_response',
    accept,
    from: role.value === 'customer' ? 'customer' : 'consultant'
  }))
  if (accept) {
    remoteControlActive.value = true
    remoteControlDirection.value = 'being_controlled'
    setupDataChannelListener()
  }
}

function stopRemoteControl() {
  if (remoteControlDirection.value === 'controlling') {
    if (dataChannel) {
      dataChannel.close()
      dataChannel = null
    }
    remoteControlActive.value = false
    remoteControlDirection.value = null
    ws.send(JSON.stringify({ action: 'remote_control_stop' }))
  } else {
    ws.send(JSON.stringify({ action: 'remote_control_stop' }))
    remoteControlActive.value = false
    remoteControlDirection.value = null
  }
}

function setupDataChannelListener() {
  if (!pc) return
  pc.ondatachannel = null
  pc.ondatachannel = (event) => {
    const channel = event.channel
    console.log('【被控端】DataChannel 连接建立，标签:', channel.label)
    channel.onmessage = (e) => {
      const data = JSON.parse(e.data)
      console.log('【被控端】收到远程控制事件:', data.type)
      simulateEvent(data)
    }
    channel.onopen = () => {
      remoteControlActive.value = true
      remoteControlDirection.value = 'being_controlled'
      console.log('【被控端】DataChannel 已打开')
    }
    channel.onclose = () => {
      remoteControlActive.value = false
      remoteControlDirection.value = null
      console.log('【被控端】DataChannel 已关闭')
    }
  }
}

function startControlling(channel) {
  dataChannel = channel
  console.log('【远程控制】DataChannel 已创建，等待打开...')

  dataChannel.onopen = () => {
    isDataChannelOpen = true
    remoteControlActive.value = true
    remoteControlDirection.value = 'controlling'
    ElMessage.success('远程控制已建立')
    console.log('【远程控制】DataChannel 已打开，开始监听事件')
    bindControlEvents()
  }

  dataChannel.onclose = () => {
    isDataChannelOpen = false
    remoteControlActive.value = false
    remoteControlDirection.value = null
    ElMessage.info('远程控制已断开')
    unbindControlEvents()
  }

  if (dataChannel.readyState === 'open') {
    dataChannel.onopen()
  }
}

let controlEventListeners = null

function bindControlEvents() {
  if (controlEventListeners) return
  const sendEvent = (type, e) => {
    if (!isDataChannelOpen || !dataChannel) return
    const rect = document.documentElement.getBoundingClientRect()
    const percentX = e.clientX / rect.width
    const percentY = e.clientY / rect.height
    const data = {
      type,
      percentX,
      percentY,
      button: e.button || 0,
      key: e.key || '',
      code: e.code || '',
      altKey: e.altKey || false,
      ctrlKey: e.ctrlKey || false,
      shiftKey: e.shiftKey || false,
      metaKey: e.metaKey || false,
      deltaX: e.deltaX || 0,
      deltaY: e.deltaY || 0
    }
    try {
      dataChannel.send(JSON.stringify(data))
      console.log('【控制端】发送事件:', data.type, `(${(percentX*100).toFixed(2)}%, ${(percentY*100).toFixed(2)}%)`, '目标:', e.target)
    } catch (err) {
      console.error('【控制端】发送失败:', err)
    }
  }

  const clickHandler = (e) => { console.log('【控制端】捕获 click 事件'); sendEvent('click', e) }
  const mousedownHandler = (e) => { console.log('【控制端】捕获 mousedown 事件'); sendEvent('mousedown', e) }
  const mouseupHandler = (e) => { console.log('【控制端】捕获 mouseup 事件'); sendEvent('mouseup', e) }
  const mouseMoveHandler = (e) => sendEvent('mousemove', e)
  const wheelHandler = (e) => sendEvent('wheel', e)
  const keydownHandler = (e) => {
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return
    sendEvent('keydown', e)
  }
  const keyupHandler = (e) => {
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return
    sendEvent('keyup', e)
  }

  document.addEventListener('click', clickHandler, true)
  document.addEventListener('mousedown', mousedownHandler, true)
  document.addEventListener('mouseup', mouseupHandler, true)
  document.addEventListener('mousemove', mouseMoveHandler)
  document.addEventListener('wheel', wheelHandler)
  document.addEventListener('keydown', keydownHandler)
  document.addEventListener('keyup', keyupHandler)

  controlEventListeners = {
    clickHandler, mousedownHandler, mouseupHandler, mouseMoveHandler,
    wheelHandler, keydownHandler, keyupHandler
  }
  console.log('【控制端】事件监听已绑定（含捕获阶段）')
}

function unbindControlEvents() {
  if (!controlEventListeners) return
  const {
    clickHandler, mousedownHandler, mouseupHandler, mouseMoveHandler,
    wheelHandler, keydownHandler, keyupHandler
  } = controlEventListeners
  document.removeEventListener('click', clickHandler, true)
  document.removeEventListener('mousedown', mousedownHandler, true)
  document.removeEventListener('mouseup', mouseupHandler, true)
  document.removeEventListener('mousemove', mouseMoveHandler)
  document.removeEventListener('wheel', wheelHandler)
  document.removeEventListener('keydown', keydownHandler)
  document.removeEventListener('keyup', keyupHandler)
  controlEventListeners = null
}

function simulateEvent(data) {
  console.log('【被控端】收到模拟请求:', data.type, data)

  const rect = document.documentElement.getBoundingClientRect()
  const x = data.percentX * rect.width
  const y = data.percentY * rect.height

  let target = document.elementFromPoint(x, y)
  if (!target) {
    console.warn('【被控端】坐标处无元素，使用 body')
    target = document.body
  }
  console.log('【被控端】模拟事件:', data.type, '目标:', target.tagName, `(${x.toFixed(0)}, ${y.toFixed(0)})`, '元素:', target)

  if (data.type === 'click') {
    const downEvent = new MouseEvent('mousedown', {
      clientX: x,
      clientY: y,
      button: data.button || 0,
      buttons: 1,
      which: 1,
      bubbles: true,
      cancelable: true,
      view: window
    })
    target.dispatchEvent(downEvent)

    const upEvent = new MouseEvent('mouseup', {
      clientX: x,
      clientY: y,
      button: data.button || 0,
      buttons: 0,
      which: 1,
      bubbles: true,
      cancelable: true,
      view: window
    })
    target.dispatchEvent(upEvent)

    const clickEvent = new MouseEvent('click', {
      clientX: x,
      clientY: y,
      button: data.button || 0,
      buttons: 0,
      which: 1,
      bubbles: true,
      cancelable: true,
      view: window
    })
    target.dispatchEvent(clickEvent)
  } else if (data.type.startsWith('mouse')) {
    const event = new MouseEvent(data.type, {
      clientX: x,
      clientY: y,
      button: data.button || 0,
      buttons: data.type === 'mousedown' ? 1 : 0,
      which: 1,
      bubbles: true,
      cancelable: true,
      view: window
    })
    target.dispatchEvent(event)
  } else if (data.type === 'wheel') {
    const event = new WheelEvent('wheel', {
      clientX: x,
      clientY: y,
      deltaX: data.deltaX || 0,
      deltaY: data.deltaY || 0,
      bubbles: true,
      cancelable: true,
      view: window
    })
    target.dispatchEvent(event)
  } else if (data.type.startsWith('key')) {
    const event = new KeyboardEvent(data.type, {
      key: data.key,
      code: data.code,
      altKey: data.altKey,
      ctrlKey: data.ctrlKey,
      shiftKey: data.shiftKey,
      metaKey: data.metaKey,
      bubbles: true,
      cancelable: true,
      view: window
    })
    target.dispatchEvent(event)
  } else {
    console.warn('【被控端】未知事件类型:', data.type)
  }
}

// ========== WebRTC 信令 ==========
async function handleWebRTCOffer(roomId, sdp) {
  try {
    if (pc && pc.signalingState !== 'closed') {
      console.log('复用现有 PeerConnection 进行重新协商')
      setupDataChannelListener()
      await pc.setRemoteDescription(new RTCSessionDescription(sdp))
      const answer = await pc.createAnswer()
      await pc.setLocalDescription(answer)
      ws.send(JSON.stringify({ action: 'webrtc_answer', roomId, sdp: answer }))
      return
    }

    if (pc) { pc.close(); pc = null }
    pc = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' }
      ]
    })
    pc.onicecandidate = (event) => {
      if (event.candidate) {
        ws.send(JSON.stringify({ action: 'webrtc_ice_candidate', roomId, candidate: event.candidate }))
      }
    }
    pc.ontrack = (event) => {
      const stream = event.streams[0]
      console.log('收到远程流，轨道数:', stream.getTracks().length)
      const setVideo = () => {
        if (remoteVideoRef.value) {
          remoteVideoRef.value.srcObject = stream
          remoteVideoRef.value.play().catch(e => console.warn('播放失败', e))
          isReceivingScreen.value = true
          return true
        }
        return false
      }
      if (!setVideo()) {
        isReceivingScreen.value = true
        nextTick(() => {
          if (!setVideo()) setTimeout(setVideo, 100)
        })
      }
    }
    setupDataChannelListener()

    await pc.setRemoteDescription(new RTCSessionDescription(sdp))
    const answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)
    ws.send(JSON.stringify({ action: 'webrtc_answer', roomId, sdp: answer }))
  } catch (error) {
    console.error('处理 Offer 失败:', error)
    ElMessage.error('接收屏幕共享失败: ' + error.message)
  }
}

async function handleWebRTCAnswer(sdp) {
  if (!pc) return
  try {
    await pc.setRemoteDescription(new RTCSessionDescription(sdp))
    console.log('远程描述设置成功')
    if (remoteControlActive && remoteControlDirection.value === 'controlling' && !dataChannel) {
      dataChannel = pc.createDataChannel('remoteControl')
      startControlling(dataChannel)
    }
  } catch (error) {
    console.error('设置 Answer 失败:', error)
  }
}

async function handleWebRTCIceCandidate(candidate) {
  if (!pc) return
  try {
    await pc.addIceCandidate(new RTCIceCandidate(candidate))
    console.log('ICE candidate 添加成功')
  } catch (error) {
    console.error('ICE candidate 添加失败:', error)
  }
}

function closeWebRTC() {
  if (pc) { pc.close(); pc = null }
  isReceivingScreen.value = false
  if (remoteVideoRef.value) {
    remoteVideoRef.value.srcObject = null
  }
  if (dataChannel) {
    unbindControlEvents()
    dataChannel.close()
    dataChannel = null
  }
  isDataChannelOpen = false
  remoteControlActive.value = false
  remoteControlDirection.value = null
  console.log('WebRTC 已关闭')
}

// ========== 处理 WebSocket 消息 ==========
async function handleWebSocketMessage(msg) {
  console.log('【WS消息】', msg)
  const currentRoomId = shareInfo.roomId || joinForm.roomId || ''

  if (msg.action === 'control_transfer') {
    const currentUserType = role.value === 'customer' ? 'owner' : 'participant'
    const currentUserId = userStore.userBizId || 'admin'
    isController.value = (msg.holderType === currentUserType && msg.holderId === currentUserId)
    ElMessage.info(`控制权已切换至${msg.holderType === 'owner' ? '客户' : '顾问'}`)
    return
  }
  if (msg.action === 'init_sync') {
    const currentUserType = role.value === 'customer' ? 'owner' : 'participant'
    const currentUserId = userStore.userBizId || 'admin'
    isController.value = (msg.holderType === currentUserType && msg.holderId === currentUserId)
    if (msg.currentPage) {
      const data = msg.currentPage.data
      currentPage.value = data.currentPage || 0
      scrollRatio.value = data.scrollRatio || 0
      zoomLevel.value = data.zoomLevel || 1
      desensitizationEnabled.value = data.desensitizationEnabled || false
      applyScroll(scrollRatio.value)
      if (desensitizationEnabled.value) applyDesensitization()
      else restoreOriginalContent()
      ElMessage.success('状态已同步')
    }
    return
  }
  if (msg.action === 'turn_the_page' && msg.currentPage) {
    currentPage.value = msg.currentPage.data.currentPage
    scrollRatio.value = 0
    applyScroll(0)
    if (desensitizationEnabled.value) applyDesensitization()
    else restoreOriginalContent()
    return
  }
  if (msg.action === 'scroll' && msg.currentPage) {
    scrollRatio.value = msg.currentPage.data.scrollRatio
    applyScroll(scrollRatio.value)
    return
  }
  if (msg.action === 'scaling' && msg.currentPage) {
    zoomLevel.value = msg.currentPage.data.zoomLevel
    return
  }
  if (msg.action === 'desensitization_switch') {
    desensitizationEnabled.value = msg.enabled
    if (desensitizationEnabled.value) await applyDesensitization()
    else restoreOriginalContent()
    ElMessage.info(`脱敏${msg.enabled ? '开启' : '关闭'}`)
    return
  }
  if (msg.action === 'end_sharing') {
    console.log('收到结束共享消息，当前录制状态:', recordingStatus.value)
    ElMessage.warning('共享已结束')
    if (recordingStatus.value === 'recording') {
      await stopRecordingAndUpload()
    }
    shouldReconnect = false
    if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
    if (ws) { ws.close(); ws = null }
    wsConnected.value = false
    sharingStatus.value = '已结束'
    closeWebRTC()
    return
  }
  if (msg.action === 'presenter_change') {
    handlePresenterChange(msg)
    return
  }
  if (msg.action === 'presenter_response') {
    handlePresenterResponse(msg)
    return
  }
  if (msg.action === 'presenter_invite') {
    inviteMessage.value = `${msg.from === 'customer' ? '客户' : '顾问'} 邀请您投屏，是否接受？`
    pendingInvite = msg
    inviteDialogVisible.value = true
    return
  }
  if (msg.action === 'presenter_revoke') {
    ElMessage.warning('投屏权已被收回')
    stopLocalSharing()
    presenter.value = 'none'
    return
  }
  if (msg.action === 'request_remote_control') {
    remoteControlInviteMessage.value = `${msg.from === 'customer' ? '客户' : '顾问'} 请求远程控制您的屏幕，是否接受？`
    pendingRemoteControl = msg
    remoteControlInviteVisible.value = true
    return
  }
  if (msg.action === 'remote_control_response') {
    if (msg.accept) {
      ElMessage.success('对方已接受远程控制')
      remoteControlActive.value = true
      remoteControlDirection.value = 'controlling'
      if (pc && !dataChannel) {
        dataChannel = pc.createDataChannel('remoteControl')
        startControlling(dataChannel)
        try {
          const offer = await pc.createOffer()
          await pc.setLocalDescription(offer)
          ws.send(JSON.stringify({ action: 'webrtc_offer', roomId: currentRoomId, sdp: offer }))
          console.log('【远程控制】重新协商 Offer 已发送')
        } catch (e) {
          console.error('重新协商失败', e)
          ElMessage.error('远程控制建立失败: ' + e.message)
        }
      } else {
        ElMessage.warning('无法建立远程控制，请检查连接状态')
      }
    } else {
      ElMessage.info('对方拒绝了远程控制')
    }
    return
  }
  if (msg.action === 'remote_control_stop') {
    ElMessage.info('远程控制已中断')
    if (dataChannel) {
      unbindControlEvents()
      dataChannel.close()
      dataChannel = null
    }
    remoteControlActive.value = false
    remoteControlDirection.value = null
    isDataChannelOpen = false
    return
  }
  if (msg.action === 'request_offer') {
    if (role.value === 'customer' && sharedStream) {
      const offer = await pc.createOffer()
      await pc.setLocalDescription(offer)
      ws.send(JSON.stringify({ action: 'webrtc_offer', roomId: currentRoomId, sdp: offer }))
      ws.send(JSON.stringify({ action: 'presenter_change', target: 'customer', from: 'customer' }))
    }
    return
  }
  if (msg.action === 'webrtc_offer') {
    await handleWebRTCOffer(msg.roomId, msg.sdp)
    return
  }
  if (msg.action === 'webrtc_answer') {
    await handleWebRTCAnswer(msg.sdp)
    return
  }
  if (msg.action === 'webrtc_ice_candidate') {
    await handleWebRTCIceCandidate(msg.candidate)
    return
  }
}

// ========== 应用滚动 ==========
function applyScroll(ratio) {
  const el = articleContentRef.value
  if (el) {
    const wrapper = el.closest('.article-wrapper')
    if (wrapper) {
      const maxScroll = wrapper.scrollHeight - wrapper.clientHeight
      if (maxScroll > 0) wrapper.scrollTop = ratio * maxScroll
    }
  }
}

// ========== 发送同步操作 ==========
function sendSyncAction(action, value) {
  if (!wsConnected.value || !isController.value) {
    ElMessage.warning('无控制权或未连接')
    return
  }
  const pageData = {
    url: window.location.href,
    title: currentArticle.value.title,
    page_type: 'article',
    data: {
      currentPage: currentPage.value,
      totalPages: articles.length,
      scrollRatio: scrollRatio.value,
      zoomLevel: zoomLevel.value,
      desensitizationEnabled: desensitizationEnabled.value,
      pageTitle: currentArticle.value.title,
      chartIndices: []
    }
  }
  const sendMsg = { action, currentPage: pageData }
  if (action === 'desensitization_switch') {
    sendMsg.enabled = value
    pageData.data.desensitizationEnabled = value
  } else if (action === 'turn_the_page') {
    pageData.data.currentPage = currentPage.value
  } else if (action === 'scroll') {
    scrollRatio.value = value
    pageData.data.scrollRatio = value
  } else if (action === 'scaling') {
    zoomLevel.value = value
    pageData.data.zoomLevel = value
  }
  ws.send(JSON.stringify(sendMsg))
}

// ========== 复制共享码 ==========
function copyCode() {
  if (shareInfo.roomPwd) {
    navigator.clipboard?.writeText(shareInfo.roomPwd).then(() => ElMessage.success('已复制'))
        .catch(() => {
          const input = document.createElement('input')
          input.value = shareInfo.roomPwd
          document.body.appendChild(input)
          input.select()
          document.execCommand('copy')
          document.body.removeChild(input)
          ElMessage.success('已复制')
        })
  }
}

// ========== 录制相关 ==========
async function startRecordingWithStream(stream) {
  try {
    if (recordingStatus.value === 'recording') return
    if (!shareInfo.sessionBizId) {
      ElMessage.warning('未获取到协同会话ID，无法开始录制')
      return
    }

    const startRes = await startRecording({
      bizType: 'co_session',
      bizId: shareInfo.sessionBizId
    })
    if (startRes.code !== 200) {
      ElMessage.error(startRes.msg || '创建录制任务失败')
      return
    }
    recordingTaskId.value = startRes.data
    console.log('【录制】任务创建成功，taskId:', recordingTaskId.value)

    const mimeTypes = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm', 'video/mp4']
    let mimeType = mimeTypes.find(type => MediaRecorder.isTypeSupported(type)) || ''
    const recorder = new MediaRecorder(stream, {
      mimeType,
      videoBitsPerSecond: 10000000
    })

    uploadedChunkCount.value = 0
    isStopping.value = false
    uploadPromises = []

    recorder.ondataavailable = async (event) => {
      if (event.data && event.data.size > 0) {
        const index = uploadedChunkCount.value
        uploadedChunkCount.value++

        const file = new File([event.data], `chunk_${Date.now()}.webm`, {
          type: event.data.type || 'video/webm'
        })
        console.log(`【录制】分片数据大小: ${event.data.size}, 索引: ${index}`)

        const promise = uploadChunk({
          taskId: recordingTaskId.value,
          chunkIndex: index,
          chunk: file,
          projectBizId: projectBizId.value,
          source: 'recording'
        })
        uploadPromises.push(promise)

        try {
          await promise
          console.log(`【录制】分片 ${index} 实时上传成功`)
        } catch (error) {
          console.error(`【录制】分片 ${index} 上传失败:`, error)
        }
      }
    }

    recorder.onstop = () => {
      console.log('【录制】MediaRecorder 停止，共产生', uploadedChunkCount.value, '个分片')
    }

    recorder.onerror = (event) => {
      console.error('【录制】MediaRecorder 错误:', event)
      ElMessage.error('录制发生错误')
    }

    const videoTrack = stream.getVideoTracks()[0]
    if (videoTrack) {
      videoTrack.addEventListener('ended', () => {
        if (recordingStatus.value === 'recording') {
          ElMessage.warning('屏幕共享被中断，录制将停止')
          stopRecordingAndUpload()
        }
      })
    }

    mediaRecorder.value = recorder
    recorder.start(20000)
    recordingStatus.value = 'recording'
    recordingStartTime.value = Date.now()
    recordingDuration.value = 0

    if (durationTimer) clearInterval(durationTimer)
    durationTimer = setInterval(() => {
      recordingDuration.value = Math.floor((Date.now() - recordingStartTime.value) / 1000)
    }, 1000)

    ElMessage.success('开始录制屏幕')
  } catch (error) {
    console.error('【录制】启动失败:', error)
    ElMessage.error('录制启动失败: ' + error.message)
    recordingStatus.value = 'idle'
    recordingTaskId.value = null
  }
}

async function stopRecordingAndUpload() {
  if (isStopping.value) return
  if (recordingStatus.value !== 'recording') {
    ElMessage.warning('当前没有正在进行的录制')
    return
  }
  if (!mediaRecorder.value) {
    ElMessage.warning('录制器不存在')
    return
  }

  isStopping.value = true

  const stopPromise = new Promise((resolve) => {
    const originalOnStop = mediaRecorder.value.onstop
    mediaRecorder.value.onstop = (event) => {
      if (originalOnStop) {
        originalOnStop(event)
      }
      resolve()
    }
  })

  mediaRecorder.value.stop()
  if (durationTimer) {
    clearInterval(durationTimer)
    durationTimer = null
  }

  await stopPromise

  if (uploadPromises.length === 0) {
    ElMessage.warning('没有录制到任何数据')
    recordingStatus.value = 'idle'
    isStopping.value = false
    uploadPromises = []
    return
  }

  try {
    await Promise.all(uploadPromises)
    console.log('【录制】所有分片上传完成')
  } catch (error) {
    console.error('【录制】分片上传失败:', error)
  }

  try {
    await callStopRecording()
    ElMessage.success('录制文件合并成功！')
  } catch (error) {
    console.error('【录制】合并失败:', error)
    ElMessage.error('合并失败: ' + error.message)
  } finally {
    uploadPromises = []
    isStopping.value = false
  }
}

async function callStopRecording() {
  try {
    const stopRes = await stopRecording(recordingTaskId.value)
    if (stopRes.code === 200) {
      const data = stopRes.data
      recordedFileUrl.value = data.fileUrl || ''
      recordingStatus.value = 'stopped'
      console.log('【录制】文件地址:', recordedFileUrl.value)
      try {
        const queryRes = await queryRecording(recordingTaskId.value)
        if (queryRes.code === 200) console.log('【录制】查询结果:', queryRes.data)
      } catch (e) { /* ignore */ }
    } else {
      throw new Error(stopRes.msg || '停止录制失败')
    }
  } catch (error) {
    console.error('【录制】停止录制失败:', error)
    throw error
  } finally {
    mediaRecorder.value = null
    recordingTaskId.value = null
  }
}

function toggleRecording() {
  if (recordingStatus.value === 'recording') {
    stopRecordingAndUpload()
  } else if (recordingStatus.value === 'idle') {
    ElMessage.info('录制将在投屏时自动开始')
  } else if (recordingStatus.value === 'stopped') {
    if (recordedFileUrl.value) window.open(recordedFileUrl.value, '_blank')
  }
}

// ========== 脱敏相关 ==========
async function applyDesensitization() {
  if (!desensitizationEnabled.value) {
    restoreOriginalContent()
    return
  }
  const originalHtml = originalContentMap.get(currentPage.value)
  if (!originalHtml) return
  try {
    const resourceType = 'report'
    const resourceId = route.query.reportId || null
    const res = await getDesensitizationRules(resourceType, resourceId)
    if (res.code !== 200 || !res.data || res.data.length === 0) {
      ElMessage.warning('未获取到脱敏规则')
      return
    }
    let processedHtml = originalHtml
    res.data.forEach(rule => {
      const fieldPath = rule.fieldPath || ''
      if (fieldPath.includes('name')) {
        const tempDiv = document.createElement('div')
        tempDiv.innerHTML = processedHtml
        const nameEls = tempDiv.querySelectorAll('.desensitize-name')
        nameEls.forEach(el => {
          const originalText = el.textContent
          const maskedText = applyMask(originalText, rule.maskType, rule.maskConfig)
          el.textContent = maskedText
        })
        processedHtml = tempDiv.innerHTML
      } else if (fieldPath.includes('phone')) {
        const tempDiv = document.createElement('div')
        tempDiv.innerHTML = processedHtml
        const phoneEls = tempDiv.querySelectorAll('.desensitize-phone')
        phoneEls.forEach(el => {
          const originalText = el.textContent
          const maskedText = applyMask(originalText, rule.maskType, rule.maskConfig)
          el.textContent = maskedText
        })
        processedHtml = tempDiv.innerHTML
      }
    })
    const currentArticle = articles[currentPage.value]
    if (currentArticle) currentArticle.content = processedHtml
  } catch (error) {
    console.error('应用脱敏失败:', error)
    ElMessage.error('脱敏处理失败')
  }
}

function restoreOriginalContent() {
  const originalHtml = originalContentMap.get(currentPage.value)
  if (originalHtml) {
    const currentArticle = articles[currentPage.value]
    if (currentArticle) currentArticle.content = originalHtml
  }
}

function applyMask(text, maskType, maskConfigStr) {
  if (!text) return text
  let config = {}
  try { config = maskConfigStr ? JSON.parse(maskConfigStr) : {} } catch (e) {}
  const replaceChar = config.replace_char || '*'
  switch (maskType) {
    case 'mask': {
      const suffix = config.suffix || 1
      if (text.length <= suffix) return text
      const prefixLen = text.length - suffix
      return replaceChar.repeat(prefixLen) + text.slice(-suffix)
    }
    case 'hide': return replaceChar.repeat(text.length)
    case 'replace': return replaceChar.repeat(text.length)
    case 'partial': {
      const prefix = config.prefix || 0
      const suffix = config.suffix || 0
      if (text.length <= prefix + suffix) return text
      const middleLen = text.length - prefix - suffix
      return text.slice(0, prefix) + replaceChar.repeat(middleLen) + text.slice(-suffix)
    }
    default: return text
  }
}

async function toggleDesensitization() {
  desensitizationEnabled.value = !desensitizationEnabled.value
  await applyDesensitization()
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ action: 'desensitization_switch', enabled: desensitizationEnabled.value }))
  }
}

function testRemoteClick() {
  console.log('✅✅✅ 远程控制测试按钮被点击！')
  ElMessage.success('远程控制点击成功！')
}

// ========== 生命周期 ==========
onMounted(() => {
  articles.forEach((article, index) => {
    originalContentMap.set(index, article.content)
  })
  const wrapper = document.querySelector('.article-wrapper')
  if (wrapper) {
    wrapper.addEventListener('scroll', () => {
      if (wsConnected.value && isController.value) {
        const maxScroll = wrapper.scrollHeight - wrapper.clientHeight
        if (maxScroll > 0) {
          const ratio = wrapper.scrollTop / maxScroll
          if (Math.abs(ratio - scrollRatio.value) > 0.01) {
            scrollRatio.value = ratio
            sendSyncAction('scroll', ratio)
          }
        }
      }
    })
  }
})

onBeforeUnmount(() => {
  shouldReconnect = false
  if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
  if (ws) { try { ws.close() } catch (e) {}; ws = null }
  closeWebRTC()
  if (durationTimer) clearInterval(durationTimer)
  if (mediaRecorder.value && recordingStatus.value === 'recording') {
    try { mediaRecorder.value.stop() } catch (e) {}
  }
  if (sharedStream) {
    sharedStream.getTracks().forEach(track => track.stop())
    sharedStream = null
  }
  originalContentMap.clear()
  uploadPromises = []
})
</script>

<style scoped>
.reader-container {
  position: relative;
  height: 100vh;
  background: #f7f9fc;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.reader-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  background: #fff;
  border-bottom: 1px solid #e8ecf1;
  flex-shrink: 0;
  z-index: 10;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.role-badge {
  font-size: 13px;
  padding: 2px 10px;
  border-radius: 12px;
  font-weight: 500;
}
.role-badge.customer { background: #e6f7ff; color: #1890ff; }
.role-badge.consultant { background: #f6ffed; color: #52c41a; }
.presenter-badge, .remote-badge {
  font-size: 12px;
  background: #f0f2f5;
  padding: 2px 8px;
  border-radius: 12px;
}
.header-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
}
.header-center .title {
  font-size: 16px;
  font-weight: 600;
  color: #1a1a2e;
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.header-center .page-info { font-size: 12px; color: #999; }
.header-right { display: flex; align-items: center; gap: 10px; }

.reader-body {
  flex: 1;
  overflow: hidden;
  padding: 16px 20px;
  position: relative;
}
.article-wrapper {
  height: 100%;
  overflow-y: auto;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  padding: 20px 24px;
  transition: transform 0.2s ease;
}
.article-content {
  line-height: 1.8;
  font-size: 15px;
  color: #333;
}
.article-content h2 {
  font-size: 20px;
  margin-bottom: 12px;
  color: #1a1a2e;
}
.article-content p { margin-bottom: 12px; }
.article-content img {
  max-width: 100%;
  border-radius: 8px;
  margin: 12px 0;
}
.reader-footer {
  display: flex;
  align-items: center;
  padding: 12px 24px 20px;
  background: #fff;
  border-top: 1px solid #e8ecf1;
  flex-shrink: 0;
}
.zoom-controls {
  position: fixed;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: rgba(255,255,255,0.85);
  padding: 8px;
  border-radius: 24px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  backdrop-filter: blur(4px);
  z-index: 20;
}
.zoom-level { font-size: 12px; font-weight: 500; color: #333; }

.fab-group {
  position: fixed;
  bottom: 30px;
  right: 70px;    /* 原为30px，向左移动 */
  z-index: 30;
  display: flex;
  flex-direction: column-reverse;
  gap: 12px;
  align-items: center;
}
.fab-btn {
  width: 60px;
  height: 60px;
  border: none;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  transition: all 0.3s;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  padding: 0;
}
.fab-btn:hover { transform: scale(1.05); }
.fab-btn:disabled {
  background: #d9d9d9 !important;
  color: #999 !important;
  cursor: not-allowed;
  box-shadow: none;
  opacity: 0.7;
}
.fab-btn:disabled:hover {
  transform: none;
  background: #d9d9d9 !important;
}
.fab-btn-text {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  white-space: pre-line;
  color: #fff;
}
.fab-share { background: #1890ff; }
.fab-share:hover { background: #40a9ff; }
.fab-end { background: #ff4d4f; }
.fab-end:hover { background: #ff7875; }
.fab-desensitize { background: #52c41a; }
.fab-desensitize:hover { background: #73d13d; }
.fab-join { background: #1890ff; }
.fab-join:hover { background: #40a9ff; }

.video-container {
  position: relative;
  width: 100%;
  height: 100%;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.video-container video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.controls-overlay {
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 20px;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(8px);
  padding: 10px 24px;
  border-radius: 40px;
  border: 1px solid rgba(255,255,255,0.1);
  z-index: 10;
  color: white;
}
.video-actions {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 11;
  display: flex;
  gap: 8px;
}
.nav-controls {
  display: flex;
  align-items: center;
  gap: 12px;
}
.nav-btn, .zoom-btn {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.2);
  color: white;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.2s;
  font-size: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nav-btn:not(.disabled):hover, .zoom-btn:not(:disabled):hover {
  background: rgba(255,255,255,0.25);
}
.nav-btn.disabled, .zoom-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.page-text {
  color: rgba(255,255,255,0.8);
  font-size: 14px;
  min-width: 60px;
  text-align: center;
}
.zoom-controls-overlay {
  display: flex;
  align-items: center;
  gap: 6px;
}
.zoom-controls-overlay span {
  font-size: 13px;
  min-width: 36px;
  text-align: center;
}
.status-tip {
  font-size: 13px;
  color: rgba(255,255,255,0.6);
  padding-left: 16px;
  border-left: 1px solid rgba(255,255,255,0.1);
}
.customer-info {
  background: #f6ffed;
  border: 1px solid #b7eb8f;
  border-radius: 8px;
  padding: 12px 16px;
  margin: 12px 0;
}
.customer-info p { margin: 4px 0; }

.article-wrapper::-webkit-scrollbar { width: 4px; }
.article-wrapper::-webkit-scrollbar-track { background: transparent; }
.article-wrapper::-webkit-scrollbar-thumb { background: #d0d5dd; border-radius: 4px; }
</style>