import React, { useState, useEffect, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import {
  Card, Row, Col, Button, Space, Typography, Tag, Avatar,
  Tooltip, Progress, Spin, App, Dropdown,
  Divider, List, Tabs, Input, Upload, Empty, Drawer, Select, Switch
} from 'antd';
import {
  ClockCircleOutlined, UserOutlined, TeamOutlined,
  EditOutlined, DeleteOutlined,
  SettingOutlined, FireOutlined, SyncOutlined,
  UploadOutlined, QrcodeOutlined,
  EyeOutlined, FileTextOutlined, PartitionOutlined,
  SaveOutlined, CloseOutlined,
  StarFilled, RobotOutlined, DownloadOutlined,
  CheckOutlined,
  MoreOutlined, AudioOutlined, CopyOutlined
} from '@ant-design/icons';
import MarkdownRenderer from '../components/MarkdownRenderer';
import MarkdownEditor from '../components/MarkdownEditor';
import MindMap from '../components/MindMap';
import ActionButton from '../components/ActionButton';
import AudioPlayerBar from '../components/AudioPlayerBar';
import TranscriptTimeline from '../components/TranscriptTimeline';
import apiClient from '../utils/apiClient';
import tools from '../utils/tools';
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
import QRCodeModal from '../components/QRCodeModal';
import MeetingFormDrawer from '../components/MeetingFormDrawer';
import configService from '../utils/configService';
import { AUDIO_UPLOAD_ACCEPT, uploadMeetingAudio, validateMeetingAudioFile } from '../services/meetingAudioService';

const { Title, Text } = Typography;
const { TextArea } = Input;

/* ── 发言人头像颜色池 ── */
const AVATAR_COLORS = [
  '#1677ff', '#52c41a', '#fa8c16', '#eb2f96',
  '#722ed1', '#13c2c2', '#2f54eb', '#faad14',
];
const getSpeakerColor = (speakerId) => AVATAR_COLORS[(speakerId ?? 0) % AVATAR_COLORS.length];

const getSummaryDisplayContent = (content) => {
  if (!content) return content;
  return content.replace(/^\s*#\s*(会议总结|AI总结|AI 总结)\s*\n+/i, '');
};

const generateRandomPassword = (length = 4) => {
  const charset = '0123456789';
  return Array.from({ length }, () => charset[Math.floor(Math.random() * charset.length)]).join('');
};

const TRANSCRIPT_INITIAL_RENDER_COUNT = 80;
const TRANSCRIPT_RENDER_STEP = 120;

const findTranscriptIndexByTime = (segments, timeMs) => {
  let left = 0;
  let right = segments.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    const segment = segments[mid];

    if (timeMs < segment.start_time_ms) {
      right = mid - 1;
    } else if (timeMs > segment.end_time_ms) {
      left = mid + 1;
    } else {
      return mid;
    }
  }

  return -1;
};

const MeetingDetails = ({ user }) => {
  const { meeting_id } = useParams();
  const navigate = useNavigate();
  const { message, modal } = App.useApp();

  const [meeting, setMeeting] = useState(null);
  const [loading, setLoading] = useState(true);
  const [transcript, setTranscript] = useState([]);
  const [transcriptLoading, setTranscriptLoading] = useState(false);
  const [audioUrl, setAudioUrl] = useState(null);

  // 发言人
  const [editingSpeakers, setEditingSpeakers] = useState({});
  const [speakerList, setSpeakerList] = useState([]);

  // 转录状态
  const [transcriptionStatus, setTranscriptionStatus] = useState(null);
  const [transcriptionProgress, setTranscriptionProgress] = useState(0);
  const [currentHighlightIndex, setCurrentHighlightIndex] = useState(-1);

  // AI 总结
  const [showSummaryDrawer, setShowSummaryDrawer] = useState(false);
  const [summaryLoading, setSummaryLoading] = useState(false);
  const [summaryResourcesLoading, setSummaryResourcesLoading] = useState(false);
  const [userPrompt, setUserPrompt] = useState('');
  const [promptList, setPromptList] = useState([]);
  const [selectedPromptId, setSelectedPromptId] = useState(null);
  const [summaryTaskProgress, setSummaryTaskProgress] = useState(0);
  const [summaryTaskMessage, setSummaryTaskMessage] = useState('');
  const [llmModels, setLlmModels] = useState([]);
  const [selectedModelCode, setSelectedModelCode] = useState(null);

  // Drawer 状态
  const [showSpeakerDrawer, setShowSpeakerDrawer] = useState(false);
  const [viewingPrompt, setViewingPrompt] = useState(null);
  const [editDrawerOpen, setEditDrawerOpen] = useState(false);
  const [showQRModal, setShowQRModal] = useState(false);
  const [isUploading, setIsUploading] = useState(false);
  const [uploadProgress, setUploadProgress] = useState(0);
  const [uploadStatusMessage, setUploadStatusMessage] = useState('');
  const [playbackRate, setPlaybackRate] = useState(1);
  const [accessPasswordEnabled, setAccessPasswordEnabled] = useState(false);
  const [accessPasswordDraft, setAccessPasswordDraft] = useState('');
  const [savingAccessPassword, setSavingAccessPassword] = useState(false);
  const [maxAudioSize, setMaxAudioSize] = useState(100 * 1024 * 1024);

  // 转录编辑 Drawer
  const [showTranscriptEditDrawer, setShowTranscriptEditDrawer] = useState(false);
  const [editingSegments, setEditingSegments] = useState({});

  // 总结内容编辑（同窗口）
  const [isEditingSummary, setIsEditingSummary] = useState(false);
  const [editingSummaryContent, setEditingSummaryContent] = useState('');
  const [inlineSpeakerEdit, setInlineSpeakerEdit] = useState(null);
  const [inlineSpeakerEditSegmentId, setInlineSpeakerEditSegmentId] = useState(null);
  const [inlineSpeakerValue, setInlineSpeakerValue] = useState('');
  const [inlineSegmentEditId, setInlineSegmentEditId] = useState(null);
  const [inlineSegmentValue, setInlineSegmentValue] = useState('');
  const [savingInlineEdit, setSavingInlineEdit] = useState(false);
  const [transcriptVisibleCount, setTranscriptVisibleCount] = useState(TRANSCRIPT_INITIAL_RENDER_COUNT);

  const audioRef = useRef(null);
  const transcriptRefs = useRef([]);
  const statusCheckIntervalRef = useRef(null);
  const summaryPollIntervalRef = useRef(null);
  const summaryBootstrapTimeoutRef = useRef(null);
  const activeSummaryTaskIdRef = useRef(null);
  const isMeetingOwner = user?.user_id === meeting?.creator_id;
  const creatorName = meeting?.creator_username || '未知创建人';
  const hasUploadedAudio = Boolean(audioUrl);
  const isTranscriptionRunning = ['pending', 'processing'].includes(transcriptionStatus?.status);
  const isSummaryRunning = summaryLoading;
  const displayUploadProgress = Math.max(0, Math.min(uploadProgress, 100));
  const displayTranscriptionProgress = Math.max(0, Math.min(transcriptionProgress, 100));
  const displaySummaryProgress = Math.max(0, Math.min(summaryTaskProgress, 100));
  const summaryDisabledReason = isUploading
    ? '音频上传中，暂不允许重新总结'
    : !hasUploadedAudio
      ? '请先上传音频后再总结'
      : isTranscriptionRunning
        ? '转录进行中，完成后会自动总结'
        : '';
  const isSummaryActionDisabled = isUploading || !hasUploadedAudio || isTranscriptionRunning || summaryLoading;

  /* ══════════════════ 数据获取 ══════════════════ */

  useEffect(() => {
    fetchMeetingDetails();
    fetchTranscript();
    loadAudioUploadConfig();
    return () => {
      if (statusCheckIntervalRef.current) clearInterval(statusCheckIntervalRef.current);
      if (summaryPollIntervalRef.current) clearInterval(summaryPollIntervalRef.current);
      if (summaryBootstrapTimeoutRef.current) clearTimeout(summaryBootstrapTimeoutRef.current);
    };
  }, [meeting_id]);

  const loadAudioUploadConfig = async () => {
    try {
      const nextMaxAudioSize = await configService.getMaxAudioSize();
      setMaxAudioSize(nextMaxAudioSize || 100 * 1024 * 1024);
    } catch (error) {
      setMaxAudioSize(100 * 1024 * 1024);
    }
  };

  useEffect(() => {
    if (!showSummaryDrawer) {
      return;
    }

    if (promptList.length > 0 && llmModels.length > 0) {
      return;
    }

    fetchSummaryResources();
  }, [showSummaryDrawer]);

  useEffect(() => {
    transcriptRefs.current = [];
    setTranscriptVisibleCount(Math.min(transcript.length, TRANSCRIPT_INITIAL_RENDER_COUNT));
  }, [transcript]);

  useEffect(() => {
    if (currentHighlightIndex < 0 || currentHighlightIndex < transcriptVisibleCount || transcriptVisibleCount >= transcript.length) {
      return;
    }

    setTranscriptVisibleCount((prev) => Math.min(
      transcript.length,
      Math.max(prev + TRANSCRIPT_RENDER_STEP, currentHighlightIndex + 20)
    ));
  }, [currentHighlightIndex, transcript.length, transcriptVisibleCount]);

  useEffect(() => {
    if (currentHighlightIndex < 0) {
      return;
    }

    transcriptRefs.current[currentHighlightIndex]?.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }, [currentHighlightIndex, transcriptVisibleCount]);

  const clearSummaryBootstrapPolling = () => {
    if (summaryBootstrapTimeoutRef.current) {
      clearTimeout(summaryBootstrapTimeoutRef.current);
      summaryBootstrapTimeoutRef.current = null;
    }
  };

  const applyMeetingDetailsState = (meetingData, options = {}) => {
    const { allowSummaryBootstrap = true } = options;
    setMeeting(meetingData);
    if (meetingData.prompt_id) {
      setSelectedPromptId(meetingData.prompt_id);
    }
    setAccessPasswordEnabled(Boolean(meetingData.access_password));
    setAccessPasswordDraft(meetingData.access_password || '');

    if (meetingData.transcription_status) {
      const ts = meetingData.transcription_status;
      setTranscriptionStatus(ts);
      setTranscriptionProgress(ts.progress || 0);
      if (['pending', 'processing'].includes(ts.status) && ts.task_id) {
        startStatusPolling(ts.task_id);
      }
    } else {
      setTranscriptionStatus(null);
      setTranscriptionProgress(0);
    }

    if (meetingData.llm_status) {
      const llmStatus = meetingData.llm_status;
      clearSummaryBootstrapPolling();
      setSummaryTaskProgress(llmStatus.progress || 0);
      setSummaryTaskMessage(
        llmStatus.message
        || (llmStatus.status === 'processing'
          ? 'AI 正在分析会议内容...'
          : llmStatus.status === 'pending'
            ? 'AI 总结任务排队中...'
            : '')
      );
      if (['pending', 'processing'].includes(llmStatus.status) && llmStatus.task_id) {
        startSummaryPolling(llmStatus.task_id);
      } else {
        setSummaryLoading(false);
      }
    } else if (meetingData.transcription_status?.status === 'completed' && !meetingData.summary) {
      if (!activeSummaryTaskIdRef.current) {
        setSummaryLoading(true);
        setSummaryTaskProgress(0);
        setSummaryTaskMessage('转录完成，正在启动 AI 分析...');
      }
      if (allowSummaryBootstrap) {
        scheduleSummaryBootstrapPolling();
      }
    } else {
      clearSummaryBootstrapPolling();
      if (!activeSummaryTaskIdRef.current) {
        setSummaryLoading(false);
        setSummaryTaskProgress(0);
        setSummaryTaskMessage('');
      }
    }

    const hasAudioFile = Boolean(meetingData.audio_file_path && String(meetingData.audio_file_path).length > 5);
    setAudioUrl(hasAudioFile ? buildApiUrl(`${API_ENDPOINTS.MEETINGS.AUDIO(meeting_id)}/stream`) : null);
  };

  const scheduleSummaryBootstrapPolling = (attempt = 0) => {
    if (summaryPollIntervalRef.current || activeSummaryTaskIdRef.current) {
      return;
    }
    clearSummaryBootstrapPolling();
    if (attempt >= 10) {
      setSummaryLoading(false);
      setSummaryTaskMessage('');
      return;
    }
    summaryBootstrapTimeoutRef.current = setTimeout(async () => {
      summaryBootstrapTimeoutRef.current = null;
      try {
        const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meeting_id)));
        const meetingData = response.data;
        applyMeetingDetailsState(meetingData, { allowSummaryBootstrap: false });
        if (meetingData.llm_status || meetingData.summary) {
          return;
        }
      } catch {
        if (attempt >= 9) {
          setSummaryLoading(false);
          setSummaryTaskMessage('');
          return;
        }
      }
      scheduleSummaryBootstrapPolling(attempt + 1);
    }, attempt === 0 ? 1200 : 2000);
  };

  const fetchMeetingDetails = async (options = {}) => {
    const { showPageLoading = true } = options;
    try {
      if (showPageLoading) {
        setLoading(true);
      }
      const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meeting_id)));
      applyMeetingDetailsState(response.data);
      return response.data;
    } catch {
      message.error('加载会议详情失败');
      return null;
    } finally {
      if (showPageLoading) {
        setLoading(false);
      }
    }
  };

  const fetchTranscript = async () => {
    setTranscriptLoading(true);
    try {
      const res = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.TRANSCRIPT(meeting_id)));
      const segments = Array.isArray(res.data) ? res.data : [];
      setTranscript(segments);

      const speakerMap = new Map();
      segments.forEach((segment) => {
        if (segment?.speaker_id == null || speakerMap.has(segment.speaker_id)) {
          return;
        }
        speakerMap.set(segment.speaker_id, {
          speaker_id: segment.speaker_id,
          speaker_tag: segment.speaker_tag || `发言人 ${segment.speaker_id}`,
        });
      });

      const list = Array.from(speakerMap.values()).sort((a, b) => a.speaker_id - b.speaker_id);
      setSpeakerList(list);
      const init = {};
      list.forEach(s => init[s.speaker_id] = s.speaker_tag);
      setEditingSpeakers(init);
    } catch {
      setTranscript([]);
      setSpeakerList([]);
      setEditingSpeakers({});
    } finally {
      setTranscriptLoading(false);
    }
  };

  const startStatusPolling = (taskId) => {
    if (statusCheckIntervalRef.current) clearInterval(statusCheckIntervalRef.current);
    const interval = setInterval(async () => {
      try {
        const res = await apiClient.get(buildApiUrl(API_ENDPOINTS.TASKS.TRANSCRIPTION_STATUS(taskId)));
        const status = res.data;
        setTranscriptionStatus(status);
        setTranscriptionProgress(status.progress || 0);
        setMeeting(prev => (prev ? { ...prev, transcription_status: status } : prev));
        if (['completed', 'failed', 'error', 'cancelled'].includes(status.status)) {
          clearInterval(interval);
          statusCheckIntervalRef.current = null;
          if (status.status === 'completed') {
            fetchTranscript();
            fetchMeetingDetails({ showPageLoading: false });
          }
        }
      } catch {
        clearInterval(interval);
        statusCheckIntervalRef.current = null;
      }
    }, 3000);
    statusCheckIntervalRef.current = interval;
  };

  const fetchPromptList = async () => {
    try {
      const res = await apiClient.get(buildApiUrl(API_ENDPOINTS.PROMPTS.ACTIVE('MEETING_TASK')));
      setPromptList(res.data.prompts || []);
      const def = res.data.prompts?.find(p => p.is_default) || res.data.prompts?.[0];
      if (def) setSelectedPromptId(def.id);
    } catch (error) {
      console.debug('加载提示词列表失败:', error);
    }
  };

  const fetchLlmModels = async () => {
    try {
      const res = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.LLM_MODELS));
      const models = Array.isArray(res.data) ? res.data : (res.data?.models || []);
      setLlmModels(models);
      const def = models.find(m => m.is_default);
      if (def) setSelectedModelCode(def.model_code);
    } catch (error) {
      console.debug('加载模型列表失败:', error);
    }
  };

  const fetchSummaryResources = async () => {
    setSummaryResourcesLoading(true);
    try {
      await Promise.allSettled([
        promptList.length > 0 ? Promise.resolve() : fetchPromptList(),
        llmModels.length > 0 ? Promise.resolve() : fetchLlmModels(),
      ]);
    } finally {
      setSummaryResourcesLoading(false);
    }
  };

  const startSummaryPolling = (taskId, options = {}) => {
    const { closeDrawerOnComplete = false } = options;
    if (!taskId) return;
    if (summaryPollIntervalRef.current && activeSummaryTaskIdRef.current === taskId) return;
    if (summaryPollIntervalRef.current) clearInterval(summaryPollIntervalRef.current);

    activeSummaryTaskIdRef.current = taskId;
    setSummaryLoading(true);

    const poll = async () => {
      try {
        const statusRes = await apiClient.get(buildApiUrl(API_ENDPOINTS.TASKS.SUMMARY_STATUS(taskId)));
        const status = statusRes.data;
        setSummaryTaskProgress(status.progress || 0);
        setSummaryTaskMessage(status.message || 'AI 正在分析会议内容...');
        setMeeting(prev => (prev ? { ...prev, llm_status: status } : prev));

        if (status.status === 'completed') {
          clearInterval(interval);
          summaryPollIntervalRef.current = null;
          activeSummaryTaskIdRef.current = null;
          setSummaryLoading(false);
          if (closeDrawerOnComplete) {
            setShowSummaryDrawer(false);
          }
          fetchMeetingDetails({ showPageLoading: false });
        } else if (status.status === 'failed') {
          clearInterval(interval);
          summaryPollIntervalRef.current = null;
          activeSummaryTaskIdRef.current = null;
          setSummaryLoading(false);
          message.error(status.error_message || '生成总结失败');
        }
      } catch (error) {
        clearInterval(interval);
        summaryPollIntervalRef.current = null;
        activeSummaryTaskIdRef.current = null;
        setSummaryLoading(false);
        message.error(error?.response?.data?.message || '获取总结状态失败');
      }
    };

    const interval = setInterval(poll, 3000);
    summaryPollIntervalRef.current = interval;
    poll();
  };

  /* ══════════════════ 操作 ══════════════════ */

  const handleTimeUpdate = () => {
    if (!audioRef.current) return;
    const timeMs = audioRef.current.currentTime * 1000;
    const idx = findTranscriptIndexByTime(transcript, timeMs);
    if (idx !== -1 && idx !== currentHighlightIndex) {
      setCurrentHighlightIndex(idx);
      transcriptRefs.current[idx]?.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
  };

  const handleTranscriptScroll = (event) => {
    if (transcriptVisibleCount >= transcript.length) {
      return;
    }

    const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
    if (scrollHeight - scrollTop - clientHeight > 240) {
      return;
    }

    setTranscriptVisibleCount((prev) => Math.min(transcript.length, prev + TRANSCRIPT_RENDER_STEP));
  };

  const jumpToTime = (ms) => {
    if (audioRef.current) {
      audioRef.current.currentTime = ms / 1000;
      audioRef.current.play();
    }
  };

  const handleUploadAudio = async (file) => {
    const validationMessage = validateMeetingAudioFile(file, maxAudioSize, configService.formatFileSize.bind(configService));
    if (validationMessage) {
      message.warning(validationMessage);
      throw new Error(validationMessage);
    }

    setIsUploading(true);
    setUploadProgress(0);
    setUploadStatusMessage('正在上传音频文件...');
    try {
      await uploadMeetingAudio({
        meetingId: meeting_id,
        file,
        promptId: meeting?.prompt_id,
        modelCode: selectedModelCode,
        onUploadProgress: (progressEvent) => {
          if (progressEvent.total) {
            setUploadProgress(Math.min(100, Math.round((progressEvent.loaded * 100) / progressEvent.total)));
          }
          setUploadStatusMessage('正在上传音频文件...');
        },
      });
      setUploadProgress(100);
      setUploadStatusMessage('上传完成，正在启动转录任务...');
      message.success('音频上传成功');
      setTranscript([]);
      setSpeakerList([]);
      setEditingSpeakers({});
      await fetchMeetingDetails({ showPageLoading: false });
      await fetchTranscript();
    } catch (error) {
      message.error(error?.response?.data?.message || error?.response?.data?.detail || '上传失败');
      throw error;
    } finally {
      setIsUploading(false);
      setUploadProgress(0);
      setUploadStatusMessage('');
    }
  };

  const saveAccessPassword = async () => {
    const nextPassword = accessPasswordEnabled ? accessPasswordDraft.trim() : null;
    if (accessPasswordEnabled && !nextPassword) {
      message.warning('开启访问密码后，请先输入密码');
      return;
    }

    setSavingAccessPassword(true);
    try {
      const res = await apiClient.put(
        buildApiUrl(API_ENDPOINTS.MEETINGS.ACCESS_PASSWORD(meeting_id)),
        { password: nextPassword }
      );
      const savedPassword = res.data?.password || null;
      setMeeting((prev) => (prev ? { ...prev, access_password: savedPassword } : prev));
      setAccessPasswordEnabled(Boolean(savedPassword));
      setAccessPasswordDraft(savedPassword || '');
      message.success(res.message || '访问密码已更新');
    } catch (error) {
      message.error(error?.response?.data?.message || '访问密码更新失败');
    } finally {
      setSavingAccessPassword(false);
    }
  };

  const handleAccessPasswordSwitchChange = async (checked) => {
    setAccessPasswordEnabled(checked);
    if (checked) {
      const existingPassword = (meeting?.access_password || accessPasswordDraft || '').trim();
      setAccessPasswordDraft(existingPassword || generateRandomPassword());
      return;
    }

    if (!checked) {
      setAccessPasswordDraft('');
      setSavingAccessPassword(true);
      try {
        const res = await apiClient.put(
          buildApiUrl(API_ENDPOINTS.MEETINGS.ACCESS_PASSWORD(meeting_id)),
          { password: null }
        );
        setMeeting((prev) => (prev ? { ...prev, access_password: null } : prev));
        message.success(res.message || '访问密码已关闭');
      } catch (error) {
        setAccessPasswordEnabled(true);
        setAccessPasswordDraft(meeting?.access_password || '');
        message.error(error?.response?.data?.message || '访问密码更新失败');
      } finally {
        setSavingAccessPassword(false);
      }
    }
  };

  const copyAccessPassword = async () => {
    if (!accessPasswordDraft) {
      message.warning('当前没有可复制的访问密码');
      return;
    }
    await navigator.clipboard.writeText(accessPasswordDraft);
    message.success('访问密码已复制');
  };

  const openAudioUploadPicker = () => {
    document.getElementById('audio-upload-input')?.click();
  };

  const startInlineSpeakerEdit = (speakerId, currentTag, segmentId) => {
    setInlineSpeakerEdit(speakerId);
    setInlineSpeakerEditSegmentId(`speaker-${speakerId}-${segmentId}`);
    setInlineSpeakerValue(currentTag || `发言人 ${speakerId}`);
  };

  const cancelInlineSpeakerEdit = () => {
    setInlineSpeakerEdit(null);
    setInlineSpeakerEditSegmentId(null);
    setInlineSpeakerValue('');
  };

  const saveInlineSpeakerEdit = async () => {
    if (inlineSpeakerEdit == null) return;
    const nextTag = inlineSpeakerValue.trim();
    if (!nextTag) {
      message.warning('发言人名称不能为空');
      return;
    }
    setSavingInlineEdit(true);
    try {
      await apiClient.put(buildApiUrl(`/api/meetings/${meeting_id}/speaker-tags/batch`), {
        updates: [{ speaker_id: inlineSpeakerEdit, new_tag: nextTag }]
      });
      setTranscript(prev => prev.map(item => (
        item.speaker_id === inlineSpeakerEdit
          ? { ...item, speaker_tag: nextTag }
          : item
      )));
      setSpeakerList(prev => prev.map(item => (
        item.speaker_id === inlineSpeakerEdit
          ? { ...item, speaker_tag: nextTag }
          : item
      )));
      setEditingSpeakers(prev => ({ ...prev, [inlineSpeakerEdit]: nextTag }));
      message.success('发言人名称已更新');
      cancelInlineSpeakerEdit();
    } catch (error) {
      message.error(error?.response?.data?.message || '更新发言人名称失败');
    } finally {
      setSavingInlineEdit(false);
    }
  };

  const startInlineSegmentEdit = (segment) => {
    setInlineSegmentEditId(segment.segment_id);
    setInlineSegmentValue(segment.text_content || '');
  };

  const cancelInlineSegmentEdit = () => {
    setInlineSegmentEditId(null);
    setInlineSegmentValue('');
  };

  const saveInlineSegmentEdit = async () => {
    if (inlineSegmentEditId == null) return;
    setSavingInlineEdit(true);
    try {
      await apiClient.put(buildApiUrl(`/api/meetings/${meeting_id}/transcript/batch`), {
        updates: [{ segment_id: inlineSegmentEditId, text_content: inlineSegmentValue }]
      });
      setTranscript(prev => prev.map(item => (
        item.segment_id === inlineSegmentEditId
          ? { ...item, text_content: inlineSegmentValue }
          : item
      )));
      message.success('转录内容已更新');
      cancelInlineSegmentEdit();
    } catch (error) {
      message.error(error?.response?.data?.message || '更新转录内容失败');
    } finally {
      setSavingInlineEdit(false);
    }
  };

  const changePlaybackRate = (nextRate) => {
    setPlaybackRate(nextRate);
    if (audioRef.current) {
      audioRef.current.playbackRate = nextRate;
    }
  };

  const handleStartTranscription = async () => {
    try {
      const res = await apiClient.post(buildApiUrl(`/api/meetings/${meeting_id}/transcription/start`));
      if (res.data?.task_id) {
        message.success('转录任务已启动');
        setTranscriptionStatus({ status: 'processing' });
        startStatusPolling(res.data.task_id);
      }
    } catch (e) {
      message.error(e?.response?.data?.detail || '启动转录失败');
    }
  };

  const handleDeleteMeeting = () => {
    if (!isMeetingOwner) {
      message.warning('仅会议创建人可删除会议');
      return;
    }
    modal.confirm({
      title: '删除会议',
      content: '确定要删除此会议吗？此操作无法撤销。',
      okText: '删除',
      okType: 'danger',
      onOk: async () => {
        await apiClient.delete(buildApiUrl(API_ENDPOINTS.MEETINGS.DELETE(meeting_id)));
        navigate('/dashboard');
      }
    });
  };

  const generateSummary = async () => {
    if (!isMeetingOwner) {
      message.warning('仅会议创建人可重新总结');
      return;
    }
    if (isUploading) {
      message.warning('音频上传中，暂不允许重新总结');
      return;
    }
    if (!hasUploadedAudio) {
      message.warning('请先上传音频后再总结');
      return;
    }
    if (isTranscriptionRunning) {
      message.warning('转录进行中，暂不允许重新总结');
      return;
    }
    setSummaryLoading(true);
    setSummaryTaskProgress(0);
    try {
      const res = await apiClient.post(buildApiUrl(`/api/meetings/${meeting_id}/generate-summary-async`), {
        user_prompt: userPrompt,
        prompt_id: selectedPromptId,
        model_code: selectedModelCode
      });
      startSummaryPolling(res.data.task_id, { closeDrawerOnComplete: true });
    } catch (error) {
      message.error(error?.response?.data?.message || '生成总结失败');
      setSummaryLoading(false);
    }
  };

  const openSummaryDrawer = () => {
    if (!isMeetingOwner) {
      message.warning('仅会议创建人可重新总结');
      return;
    }
    if (isUploading) {
      message.warning('音频上传中，暂不允许重新总结');
      return;
    }
    if (!hasUploadedAudio) {
      message.warning('请先上传音频后再总结');
      return;
    }
    if (isTranscriptionRunning) {
      message.warning('转录进行中，完成后会自动总结');
      return;
    }
    setShowSummaryDrawer(true);
  };

  const downloadSummaryMd = () => {
    if (!meeting?.summary) { message.warning('暂无总结内容'); return; }
    const blob = new Blob([meeting.summary], { type: 'text/markdown;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${meeting.title || 'summary'}_总结.md`;
    a.click();
    URL.revokeObjectURL(url);
  };

  const saveTranscriptEdits = async () => {
    try {
      const updates = Object.values(editingSegments).map(s => ({
        segment_id: s.segment_id,
        text_content: s.text_content,
      }));
      await apiClient.put(buildApiUrl(`/api/meetings/${meeting_id}/transcript/batch`), { updates });
      message.success('转录内容已更新');
      setShowTranscriptEditDrawer(false);
      fetchTranscript();
    } catch (error) {
      console.debug('批量更新转录失败:', error);
      message.error('更新失败');
    }
  };

  /* ── 总结内容编辑 ── */
  const openSummaryEditDrawer = () => {
    if (!isMeetingOwner) {
      message.warning('仅会议创建人可编辑总结');
      return;
    }
    setEditingSummaryContent(meeting?.summary || '');
    setIsEditingSummary(true);
  };

  const saveSummaryContent = async () => {
    if (!isMeetingOwner) {
      message.warning('仅会议创建人可编辑总结');
      return;
    }
    try {
      await apiClient.put(buildApiUrl(API_ENDPOINTS.MEETINGS.UPDATE(meeting_id)), {
        title: meeting.title,
        meeting_time: meeting.meeting_time,
        summary: editingSummaryContent,
        tags: meeting.tags?.map(t => t.name).join(',') || '',
      });
      message.success('总结已保存');
      setMeeting(prev => (prev ? { ...prev, summary: editingSummaryContent } : prev));
      setIsEditingSummary(false);
    } catch { message.error('保存失败'); }
  };

  /* ── 更多操作菜单 ── */
  const panelMoreMenuItems = [
    { key: 'delete', icon: <DeleteOutlined />, label: '删除会议', danger: true, onClick: handleDeleteMeeting },
  ];

  const audioMoreMenuItems = [
    { key: 'transcribe', icon: <AudioOutlined />, label: '智能转录', disabled: !audioUrl || isUploading || transcriptionStatus?.status === 'processing', onClick: handleStartTranscription },
    { key: 'upload', icon: <UploadOutlined />, label: isUploading ? '上传中...' : '上传音频', disabled: isUploading, onClick: openAudioUploadPicker },
  ];

  /* ══════════════════ 渲染 ══════════════════ */

  if (loading) return <div style={{ textAlign: 'center', padding: '100px' }}><Spin size="large" tip="正在加载..." /></div>;

  return (
    <div>
      {/* ── 标题 Header ── */}
      <Card
        variant="borderless"
        className="console-surface"
        style={{ marginBottom: 16 }}
        styles={{ body: { padding: '16px 24px' } }}
      >
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
              <Title level={3} style={{ margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {meeting.title}
              </Title>
              {isMeetingOwner ? (
                <ActionButton tone="edit" variant="iconSm" tooltip="编辑会议" icon={<EditOutlined />} style={{ flexShrink: 0 }} onClick={() => setEditDrawerOpen(true)} />
              ) : null}
            </div>
            <Space size={16} wrap>
              <Text type="secondary"><ClockCircleOutlined style={{ marginRight: 4 }} />{tools.formatDateTime(meeting.meeting_time)}</Text>
              <Text type="secondary">
                <UserOutlined style={{ marginRight: 4 }} />
                创建人：{creatorName}
              </Text>
              <Text type="secondary">
                <TeamOutlined style={{ marginRight: 4 }} />
                参会人：
                {meeting.attendees?.length
                  ? meeting.attendees.map(a => typeof a === 'string' ? a : a.caption).join('、')
                  : '未指定'}
              </Text>
            </Space>
          </div>

          <Space style={{ flexShrink: 0 }}>
            {isMeetingOwner ? (
              <Tooltip title={summaryDisabledReason}>
                <span>
                  <Button className="btn-pill-secondary" icon={<SyncOutlined />} onClick={openSummaryDrawer} disabled={isSummaryActionDisabled}>
                    重新总结
                  </Button>
                </span>
              </Tooltip>
            ) : null}
            <Button className="btn-pill-secondary" icon={<DownloadOutlined />} onClick={downloadSummaryMd}>下载总结</Button>
            <Tooltip title="分享二维码">
              <Button className="btn-pill-secondary" icon={<QrcodeOutlined />} onClick={() => setShowQRModal(true)} />
            </Tooltip>
            {isMeetingOwner ? (
              <Dropdown menu={{ items: panelMoreMenuItems }} trigger={['click']}>
                <Button className="btn-pill-secondary" icon={<MoreOutlined />} />
              </Dropdown>
            ) : null}
          </Space>
        </div>
      </Card>

      {isUploading && (
        <Card
          variant="borderless"
          className="console-surface"
          style={{ marginBottom: 16, border: '1px solid #91caff', background: 'linear-gradient(135deg, #f0f9ff 0%, #ffffff 100%)' }}
          styles={{ body: { padding: '14px 24px' } }}
        >
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
            <Space size={10}>
              <UploadOutlined style={{ color: '#1677ff', fontSize: 16 }} />
              <div>
                <Text strong style={{ display: 'block' }}>音频上传中</Text>
                <Text type="secondary">{uploadStatusMessage || '正在上传音频文件...'}</Text>
              </div>
            </Space>
            <Text strong style={{ color: '#1677ff' }}>{displayUploadProgress}%</Text>
          </div>
          <Progress percent={displayUploadProgress} status="active" strokeColor={{ from: '#69b1ff', to: '#1677ff' }} />
        </Card>
      )}

      {/* ── 转录进度条 ── */}
      {transcriptionStatus && ['pending', 'processing'].includes(transcriptionStatus.status) && (
        <Card variant="borderless" className="console-surface" style={{ marginBottom: 16 }} styles={{ body: { padding: '12px 24px' } }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
            <Text><SyncOutlined spin style={{ marginRight: 8 }} />正在转录中...</Text>
            <Text>{displayTranscriptionProgress}%</Text>
          </div>
          <Progress percent={displayTranscriptionProgress} status="active" size="small" />
        </Card>
      )}

      {isSummaryRunning && (
        <Card
          variant="borderless"
          className="console-surface"
          style={{ marginBottom: 16, border: '1px solid #d3adf7', background: 'linear-gradient(135deg, #f9f0ff 0%, #ffffff 100%)' }}
          styles={{ body: { padding: '14px 24px' } }}
        >
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
            <Space size={10}>
              <RobotOutlined style={{ color: '#722ed1', fontSize: 16 }} />
              <div>
                <Text strong style={{ display: 'block' }}>AI 正在分析会议内容</Text>
                <Text type="secondary">{summaryTaskMessage || '正在生成会议总结，请稍候...'}</Text>
              </div>
            </Space>
            <Text strong style={{ color: '#722ed1' }}>{displaySummaryProgress}%</Text>
          </div>
          <Progress percent={displaySummaryProgress} status="active" strokeColor={{ from: '#b37feb', to: '#722ed1' }} />
        </Card>
      )}

      {/* ── 隐藏的上传 input ── */}
      <Upload
        id="audio-upload-input"
        showUploadList={false}
        accept={AUDIO_UPLOAD_ACCEPT}
        beforeUpload={(file) => {
          const validationMessage = validateMeetingAudioFile(file, maxAudioSize, configService.formatFileSize.bind(configService));
          if (validationMessage) {
            message.warning(validationMessage);
            return Upload.LIST_IGNORE;
          }
          return true;
        }}
        customRequest={async ({ file, onSuccess, onError }) => {
          try {
            await handleUploadAudio(file);
            onSuccess?.({}, file);
          } catch (error) {
            onError?.(error);
          }
        }}
        style={{ display: 'none' }}
      >
        <span />
      </Upload>

      {/* ── 主内容：左转录 右总结 ── */}
      <Row gutter={16}>
        {/* 左列: 语音转录 */}
        <Col xs={24} lg={10}>
          <Card
            variant="borderless"
            className="console-surface"
            style={{ height: 'calc(100vh - 220px)', display: 'flex', flexDirection: 'column' }}
            styles={{ body: { display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden', padding: 0 } }}
          >
            {/* 转录标题栏 */}
            <div style={{ padding: '14px 20px 0', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <Title level={5} style={{ margin: 0 }}><AudioOutlined style={{ marginRight: 6 }} />语音转录</Title>
              {isMeetingOwner ? (
                <ActionButton tone="edit" variant="textSm" icon={<SettingOutlined />} onClick={() => setShowSpeakerDrawer(true)}>标签</ActionButton>
              ) : null}
            </div>

            {/* 音频播放器 */}
            <div style={{ padding: '8px 20px 0' }}>
              <AudioPlayerBar
                audioRef={audioRef}
                src={audioUrl}
                playbackRate={playbackRate}
                onPlaybackRateChange={changePlaybackRate}
                onTimeUpdate={handleTimeUpdate}
                onLoadedMetadata={() => {
                  if (audioRef.current) {
                    audioRef.current.playbackRate = playbackRate;
                  }
                }}
                moreMenuItems={audioMoreMenuItems}
                moreButtonDisabled={!isMeetingOwner}
                emptyText="暂无音频，可通过'更多'上传音频"
              />
            </div>

            {/* 转录时间轴 */}
            <div style={{ flex: 1, overflow: 'hidden', display: 'flex', minHeight: 0 }}>
              <TranscriptTimeline
                transcript={transcript}
                loading={transcriptLoading}
                visibleCount={transcriptVisibleCount}
                currentHighlightIndex={currentHighlightIndex}
                onJumpToTime={jumpToTime}
                onScroll={handleTranscriptScroll}
                transcriptRefs={transcriptRefs}
                getSpeakerColor={getSpeakerColor}
                showRenderHint
                fillHeight
                editable
                isMeetingOwner={isMeetingOwner}
                editing={{
                  inlineSpeakerEdit,
                  inlineSpeakerEditSegmentId,
                  inlineSpeakerValue,
                  setInlineSpeakerValue,
                  startInlineSpeakerEdit,
                  saveInlineSpeakerEdit,
                  cancelInlineSpeakerEdit,
                  inlineSegmentEditId,
                  inlineSegmentValue,
                  setInlineSegmentValue,
                  startInlineSegmentEdit,
                  saveInlineSegmentEdit,
                  cancelInlineSegmentEdit,
                  savingInlineEdit,
                }}
              />
            </div>
          </Card>
        </Col>

        {/* 右列: AI 总结 / 思维导图 */}
        <Col xs={24} lg={14}>
          <Card
            variant="borderless"
            className="console-surface"
            style={{ height: 'calc(100vh - 220px)', display: 'flex', flexDirection: 'column' }}
            styles={{ body: { display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden', padding: '12px 0 0' } }}
          >
            <Tabs
              tabPosition="left"
              className="console-tabs console-tabs-left"
              style={{ flex: 1, overflow: 'hidden' }}
              items={[
                {
                  key: 'summary',
                  label: <Tooltip title="AI 总结" placement="right"><FileTextOutlined style={{ fontSize: 18, margin: 0 }} /></Tooltip>,
                  children: (
                    <div style={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 268px)' }}>
                      {/* 操作栏 */}
                      <div style={{ display: 'flex', justifyContent: 'flex-end', padding: '0 16px 8px', flexShrink: 0 }}>
                        {isMeetingOwner ? (
                          isEditingSummary ? (
                            <Space>
                              <Button className="btn-pill-secondary" icon={<CloseOutlined />} onClick={() => setIsEditingSummary(false)}>取消</Button>
                              <Button type="primary" className="btn-pill-primary" icon={<SaveOutlined />} onClick={saveSummaryContent}>保存</Button>
                            </Space>
                          ) : (
                            <ActionButton tone="edit" variant="textSm" icon={<EditOutlined />} onClick={openSummaryEditDrawer}>编辑</ActionButton>
                          )
                        ) : null}
                      </div>
                      {/* 内容区 */}
                      <div style={{ flex: 1, overflowY: 'auto', padding: '0 16px 16px' }}>
                        {isEditingSummary ? (
                          <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
                            <MarkdownEditor
                              value={editingSummaryContent}
                              onChange={setEditingSummaryContent}
                              height={window.innerHeight - 380}
                              placeholder="在这里编写总结内容（支持 Markdown）..."
                              showImageUpload={false}
                            />
                          </div>
                        ) : meeting.summary ? (
                          <MarkdownRenderer content={getSummaryDisplayContent(meeting.summary)} />
                        ) : (
                          <Empty
                            image={Empty.PRESENTED_IMAGE_SIMPLE}
                            description='暂无 AI 总结，点击"重新总结"按钮生成'
                            style={{ marginTop: 80 }}
                          />
                        )}
                      </div>
                    </div>
                  ),
                },
                {
                  key: 'mindmap',
                  label: <Tooltip title="思维导图" placement="right"><PartitionOutlined style={{ fontSize: 18, margin: 0 }} /></Tooltip>,
                  children: (
                    <div style={{ height: 'calc(100vh - 268px)', background: '#f8fafc', borderRadius: 12, overflow: 'hidden' }}>
                      {meeting.summary ? (
                        <MindMap content={meeting.summary} title={meeting.title} />
                      ) : (
                        <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无总结内容" style={{ marginTop: 80 }} />
                      )}
                    </div>
                  ),
                },
              ]}
            />
          </Card>
        </Col>
      </Row>

      {/* ═══════════ Drawers ═══════════ */}

      {/* 总结生成 Drawer */}
      <Drawer
        title="AI 智能总结"
        placement="right"
        width={720}
        open={showSummaryDrawer}
        onClose={() => setShowSummaryDrawer(false)}
        destroyOnClose
        extra={
          isMeetingOwner ? (
            <Space>
              <Button type="primary" className="btn-pill-primary" icon={<FireOutlined />} loading={summaryLoading} onClick={generateSummary}>生成总结</Button>
            </Space>
          ) : null
        }
      >
        {/* 模板选择 */}
        <div style={{ marginBottom: 24 }}>
          <Text strong style={{ display: 'block', marginBottom: 12 }}>选择总结模板</Text>
          <Space direction="vertical" style={{ width: '100%' }}>
            {summaryResourcesLoading ? <Spin tip="正在加载可用模板..." /> : null}
            {promptList.length ? promptList.map(p => {
              const isSelected = selectedPromptId === p.id;
              const isSystem = Number(p.is_system) === 1;
              return (
                <Card
                  key={p.id}
                  size="small"
                  hoverable
                  onClick={() => setSelectedPromptId(p.id)}
                  style={{
                    borderRadius: 10,
                    cursor: 'pointer',
                    borderLeft: isSelected ? '4px solid #1677ff' : '4px solid transparent',
                    borderColor: isSelected ? '#1677ff' : isSystem ? '#93c5fd' : undefined,
                    background: isSelected ? '#e6f4ff' : isSystem ? '#eff6ff' : '#fff',
                  }}
                >
                  <Space direction="vertical" style={{ width: '100%' }}>
                    <Space style={{ justifyContent: 'space-between', width: '100%' }}>
                      <Space>
                        {isSelected && <Tag color="blue">已选</Tag>}
                        <Text strong>{p.name}</Text>
                      </Space>
                      <Space>
                        {isSystem ? <Tag color="blue">系统</Tag> : <Tag>个人</Tag>}
                        {p.is_default ? <Tag color="gold" icon={<StarFilled />}>默认</Tag> : null}
                        <ActionButton tone="view" variant="iconSm" tooltip="查看模板" icon={<EyeOutlined />} onClick={e => { e.stopPropagation(); setViewingPrompt(p); }} />
                      </Space>
                    </Space>
                    {p.desc && <Typography.Paragraph type="secondary" ellipsis={{ rows: 2 }} style={{ marginBottom: 0 }}>{p.desc}</Typography.Paragraph>}
                  </Space>
                </Card>
              );
            }) : <Empty description="暂无可用模板" image={Empty.PRESENTED_IMAGE_SIMPLE} />}
          </Space>
        </div>

        {/* LLM 模型选择 */}
        <div style={{ marginBottom: 24 }}>
          <Text strong style={{ display: 'block', marginBottom: 8 }}>
            <RobotOutlined style={{ marginRight: 4 }} />选择 AI 模型
          </Text>
          <Select
            style={{ width: '100%' }}
            value={selectedModelCode}
            onChange={setSelectedModelCode}
            placeholder="选择模型（默认使用系统配置）"
            allowClear
            loading={summaryResourcesLoading}
          >
            {llmModels.map(m => (
              <Select.Option key={m.model_code} value={m.model_code}>
                <Space>
                  {m.model_name}
                  <Text type="secondary" style={{ fontSize: 12 }}>{m.provider}</Text>
                  {m.is_default ? <Tag color="gold" className="console-tag-compact">默认</Tag> : null}
                </Space>
              </Select.Option>
            ))}
          </Select>
        </div>

        {/* 额外要求 */}
        <div style={{ marginBottom: 24 }}>
          <Text strong style={{ display: 'block', marginBottom: 8 }}>额外要求 (可选)</Text>
          <TextArea
            rows={3}
            value={userPrompt}
            onChange={e => setUserPrompt(e.target.value)}
            placeholder="例如：请重点分析会议中的决策事项..."
          />
        </div>

        {/* 生成进度 */}
        {summaryLoading && (
          <Card variant="borderless" style={{ borderRadius: 12, background: '#f6f8fa' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
              <Text><SyncOutlined spin style={{ marginRight: 8 }} />{summaryTaskMessage || 'AI 正在思考中...'}</Text>
              <Text>{summaryTaskProgress}%</Text>
            </div>
            <Progress percent={summaryTaskProgress} status="active" />
          </Card>
        )}
      </Drawer>

      {/* 发言人标签 Drawer */}
      <Drawer
        title="编辑发言人标签"
        placement="right"
        width={480}
        open={showSpeakerDrawer}
        onClose={() => setShowSpeakerDrawer(false)}
        destroyOnClose
        extra={
          isMeetingOwner ? (
            <Space>
              <Button type="primary" className="btn-pill-primary" icon={<SaveOutlined />} onClick={async () => {
                const updates = Object.entries(editingSpeakers).map(([id, tag]) => ({ speaker_id: parseInt(id), new_tag: tag }));
                await apiClient.put(buildApiUrl(`/api/meetings/${meeting_id}/speaker-tags/batch`), { updates });
                setShowSpeakerDrawer(false);
                fetchTranscript();
                message.success('更新成功');
              }}>保存</Button>
            </Space>
          ) : null
        }
      >
        <List
          dataSource={speakerList}
          renderItem={s => (
            <List.Item>
              <Space style={{ width: '100%' }}>
                <Avatar size={28} icon={<UserOutlined />} style={{ backgroundColor: getSpeakerColor(s.speaker_id) }} />
                <Text style={{ width: 80 }}>发言人 {s.speaker_id}</Text>
                <Input
                  style={{ flex: 1 }}
                  value={editingSpeakers[s.speaker_id]}
                  onChange={e => setEditingSpeakers({ ...editingSpeakers, [s.speaker_id]: e.target.value })}
                />
              </Space>
            </List.Item>
          )}
        />
      </Drawer>

      {/* 转录编辑 Drawer */}
      <Drawer
        title="编辑转录内容"
        placement="right"
        width={560}
        open={showTranscriptEditDrawer}
        onClose={() => setShowTranscriptEditDrawer(false)}
        destroyOnClose
        extra={
          <Space>
            <Button type="primary" className="btn-pill-primary" icon={<SaveOutlined />} onClick={saveTranscriptEdits}>保存</Button>
          </Space>
        }
      >
        {Object.values(editingSegments)
          .sort((a, b) => a.start_time_ms - b.start_time_ms)
          .map((seg) => {
            return (
              <Card
                key={seg.segment_id}
                size="small"
                style={{
                  marginBottom: 12,
                  borderRadius: 10,
                  borderLeft: '4px solid transparent',
                  background: '#fff',
                }}
              >
                <Space style={{ marginBottom: 8 }}>
                  <Avatar size={24} icon={<UserOutlined />} style={{ backgroundColor: getSpeakerColor(seg.speaker_id) }} />
                  <Text strong style={{ color: '#1677ff' }}>{seg.speaker_tag || `发言人 ${seg.speaker_id}`}</Text>
                  <Text type="secondary" style={{ fontSize: 12 }}>{tools.formatDuration(seg.start_time_ms / 1000)}</Text>
                </Space>
                <TextArea
                  autoSize={{ minRows: 2, maxRows: 6 }}
                  value={editingSegments[seg.segment_id]?.text_content ?? ''}
                  onChange={e => setEditingSegments({
                    ...editingSegments,
                    [seg.segment_id]: { ...editingSegments[seg.segment_id], text_content: e.target.value }
                  })}
                />
              </Card>
            );
          })}
      </Drawer>

      {/* 提示词预览 Drawer */}
      <Drawer
        title="查看提示词定义"
        placement="right"
        width={760}
        open={Boolean(viewingPrompt)}
        onClose={() => setViewingPrompt(null)}
      >
        {viewingPrompt && (
          <div>
            <div style={{ marginBottom: 16 }}>
              <Text type="secondary" style={{ fontSize: 12 }}>名称</Text>
              <div style={{ fontSize: 16, fontWeight: 600 }}>{viewingPrompt.name}</div>
            </div>
            <Space style={{ marginBottom: 16 }}>
              {Number(viewingPrompt.is_system) === 1 ? <Tag color="blue">系统</Tag> : <Tag>个人</Tag>}
              {Number(viewingPrompt.is_system) === 1 && Number(viewingPrompt.is_default) === 1 && <Tag color="gold" icon={<StarFilled />}>默认</Tag>}
            </Space>
            {viewingPrompt.desc && (
              <div style={{ marginBottom: 16 }}>
                <Text type="secondary" style={{ fontSize: 12 }}>描述</Text>
                <div style={{ color: 'rgba(0,0,0,0.72)' }}>{viewingPrompt.desc}</div>
              </div>
            )}
            <div>
              <Text type="secondary" style={{ fontSize: 12, marginBottom: 8, display: 'block' }}>提示词内容</Text>
              <div style={{ padding: 16, border: '1px solid #e5e7eb', borderRadius: 8, background: '#fafbfc', maxHeight: 520, overflowY: 'auto' }}>
                <MarkdownRenderer content={viewingPrompt.content || ''} />
              </div>
            </div>
          </div>
        )}
      </Drawer>

      <QRCodeModal open={showQRModal} onClose={() => setShowQRModal(false)} url={`${window.location.origin}/meetings/preview/${meeting_id}`}>
        {isMeetingOwner ? (
          <>
            <Divider style={{ margin: '0 0 16px' }} />
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
              <Text strong>访问密码保护</Text>
              <Switch
                checked={accessPasswordEnabled}
                checkedChildren="已开启"
                unCheckedChildren="已关闭"
                loading={savingAccessPassword}
                onChange={handleAccessPasswordSwitchChange}
              />
            </div>
            {accessPasswordEnabled ? (
              <>
                <Space.Compact style={{ width: '100%' }}>
                  <Input.Password
                    value={accessPasswordDraft}
                    onChange={(e) => setAccessPasswordDraft(e.target.value)}
                    placeholder="请输入访问密码"
                  />
                  <ActionButton tone="view" variant="iconLg" tooltip="复制密码" icon={<CopyOutlined />} disabled={!accessPasswordDraft} onClick={copyAccessPassword} />
                  <ActionButton tone="edit" variant="iconLg" tooltip="保存密码" icon={<SaveOutlined />} loading={savingAccessPassword} onClick={saveAccessPassword} />
                </Space.Compact>
                <Text type="secondary" style={{ display: 'block', marginTop: 10 }}>
                  开启后，访客打开分享链接时需要输入这个密码
                </Text>
              </>
            ) : (
              <Text type="secondary">关闭后，任何拿到链接的人都可以直接查看预览页</Text>
            )}
          </>
        ) : meeting?.access_password ? (
          <>
            <Divider style={{ margin: '0 0 16px' }} />
            <Text type="secondary">该分享链接已启用访问密码，密码由会议创建人管理。</Text>
          </>
        ) : null}
      </QRCodeModal>
      <MeetingFormDrawer
        open={editDrawerOpen && isMeetingOwner}
        onClose={() => setEditDrawerOpen(false)}
        meetingId={meeting_id}
        user={user}
        onSuccess={() => fetchMeetingDetails({ showPageLoading: false })}
      />
    </div>
  );
};

export default MeetingDetails;
