import React, { useEffect, useState, useRef } from 'react';
import { flushSync } from 'react-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import Sidebar from '../components/Sidebar';
import { 
  Check, Printer, Undo, Redo, Bold, Italic, Underline, 
  AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon,
  Video, Play, Pause, Plus, X, ChevronLeft, Download,
  Bot, Mic, MicOff, ImagePlus, Sparkles, Send
} from 'lucide-react';
import { User, Report, Template, CapturedFrame, SystemSettings, FormField, DEFAULT_FORM_FIELDS } from '../types';
import { defaultReportContent } from '../utils/defaultContent';
import { printDocument } from '../utils/print';
import { storage } from '../utils/storage';
import { canEditReport, getUsableTemplates } from '../utils/permissions';
import { getReport, saveReportToApi } from '../api/reports';
import { ApiError } from '../api/client';
import { createTemplate, listTemplates } from '../api/templates';
import { getSystemSettings } from '../api/settings';
import { createAiChatCompletion } from '../api/ai';
import { getSpeechIatWebSocketUrl } from '../api/speech';
import { getFieldLibrary, updateFieldLibrary } from '../api/library';
import { listFiles, uploadFileResource } from '../api/files';
import { isLocalFallbackEnabled } from '../config/runtime';
import { diffChars } from 'diff';

type AudioWindow = Window & typeof globalThis & {
  webkitAudioContext?: typeof AudioContext;
};

const getApiErrorMessage = (error: unknown, fallback: string) => {
  if (error instanceof ApiError) {
    if (error.status === 401) return '登录状态已失效，请重新登录后再保存。';
    return error.message || fallback;
  }
  if (error instanceof Error) return error.message || fallback;
  return fallback;
};

export default function ReportEditor() {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const reportId = searchParams.get('id');
  const restoreFlag = searchParams.get('restore');
  
  const [currentUser, setCurrentUser] = useState<User | null>(null);
  const [reportData, setReportData] = useState<Partial<Report>>({
    title: '腹腔镜胆囊切除术报告',
    patientName: '',
    hospitalId: '',
    patientGender: '',
    patientAge: '',
    department: '',
    bedNumber: '',
    surgeryDate: '',
    startHour: '',
    startMinute: '',
    endHour: '',
    endMinute: '',
    surgeon: [],
    assistant: [],
    anesthesiologist: [],
    anesthesiaType: '',
    reportNote: '',
    status: 'draft'
  });

  const [templates, setTemplates] = useState<Template[]>([]);
  const [videos, setVideos] = useState<{id: string, name: string, url: string, duration: number, fileId?: string}[]>([]);
  const [currentVideoIndex, setCurrentVideoIndex] = useState(-1);
  const [capturedFrames, setCapturedFrames] = useState<CapturedFrame[]>([]);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [isSaved, setIsSaved] = useState(false);
  const [exportModalOpen, setExportModalOpen] = useState(false);
  const [loadedTemplateId, setLoadedTemplateId] = useState('');
  const [pendingTemplateId, setPendingTemplateId] = useState<string | null>(null);
  const prevVideoCountRef = useRef(0);
  
  const [activeTab, setActiveTab] = useState<'info' | 'video' | 'ai'>('info');
  const [activeFieldKey, setActiveFieldKey] = useState<string | null>(null);

  // AI 撰写相关核心状态
  const [chatInput, setChatInput] = useState<string>('');
  const [chatMessages, setChatMessages] = useState<{id: string, role: 'user'|'model', content: string, images?: string[]}[]>([]);
  const [isGenerating, setIsGenerating] = useState(false);

  const [aiTargetRegion, setAiTargetRegion] = useState<string>('none');
  const [aiModifyEnabled, setAiModifyEnabled] = useState(true);
  const [isListening, setIsListening] = useState(false);
  const [aiUploadedImages, setAiUploadedImages] = useState<{id: number, dataUrl: string}[]>([]);
  const [editorImages, setEditorImages] = useState<{id: string, src: string}[]>([]);
  const [aiSelectedEditorImages, setAiSelectedEditorImages] = useState<string[]>([]);
  const xfWsRef = useRef<WebSocket | null>(null);
  const xfAudioContextRef = useRef<AudioContext | null>(null);
  const xfMediaStreamRef = useRef<MediaStream | null>(null);
  const [quickPrompts, setQuickPrompts] = useState<string[]>([
    '请完善报告内容', '请对内容做如下修改：'
  ]);
  const [isEditingPrompts, setIsEditingPrompts] = useState(false);
  const [diffModal, setDiffModal] = useState<{isOpen: boolean, originalHtml: string, newHtml: string, targetId: string} | null>(null);
  const [lastExchangeLog, setLastExchangeLog] = useState<{
    startTime: string;
    modelConfig: { provider: string; endpoint: string; modelName: string };
    requestPayload: any;
    responsePayload: any | null;
    errorDetail: { status: number; statusText: string; responseText: string; message: string } | null;
  } | null>(null);

  useEffect(() => {
    stateRef.current.chatMessages = chatMessages;
  }, [chatMessages]);

  // 监听编辑器中已插入的图片，同步到 AI 面板
  useEffect(() => {
    if (!editorRef.current) return;
    const updateEditorImages = () => {
      if (!editorRef.current) return;
      const imgs = Array.from(editorRef.current.querySelectorAll('.image-placeholder.has-image img'))
        .map((img, idx) => ({ id: `editor-img-${idx}-${(img as HTMLImageElement).src.slice(-16)}`, src: (img as HTMLImageElement).src }))
        .filter(img => img.src);
      setEditorImages(prev => {
        const same = prev.length === imgs.length && prev.every((p, i) => p.src === imgs[i]?.src);
        if (same) return prev;
        // 清除已不存在的选中项
        setAiSelectedEditorImages(prevSelected => prevSelected.filter(id => imgs.some(img => img.id === id)));
        return imgs;
      });
    };
    updateEditorImages();
    const observer = new MutationObserver(updateEditorImages);
    observer.observe(editorRef.current, { childList: true, subtree: true, attributes: true });
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    stateRef.current.chatInput = chatInput;
  }, [chatInput]);

  // 切换到 AI 面板时强制同步编辑器中的图片
  useEffect(() => {
    if (activeTab !== 'ai' || !editorRef.current) return;
    const imgs = Array.from(editorRef.current.querySelectorAll('.image-placeholder.has-image img'))
      .map((img, idx) => ({ id: `editor-img-${idx}-${(img as HTMLImageElement).src.slice(-16)}`, src: (img as HTMLImageElement).src }))
      .filter(img => img.src);
    setEditorImages(prev => {
      const same = prev.length === imgs.length && prev.every((p, i) => p.src === imgs[i]?.src);
      return same ? prev : imgs;
    });
  }, [activeTab]);

  useEffect(() => {
    if (!editorRef.current) return;
    const allFields = editorRef.current.querySelectorAll('.field-value');
    allFields.forEach(el => {
      (el as HTMLElement).style.backgroundColor = '';
      (el as HTMLElement).style.outline = '';
      (el as HTMLElement).style.outlineOffset = '';
    });
    if (activeFieldKey) {
      const targetEl = editorRef.current.querySelector(`.field-value[data-bind="${activeFieldKey}"]`) as HTMLElement;
      if (targetEl) {
        targetEl.style.backgroundColor = '#f1f5f9';
        targetEl.style.outline = '1px solid #94a3b8';
        targetEl.style.outlineOffset = '1px';
        targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
    }
  }, [activeFieldKey]);

  const [multiSelectOptions, setMultiSelectOptions] = useState<Record<string, string[]>>({
    surgeon: ['张医生', '李医生', '王医生'],
    assistant: ['赵医生', '钱医生', '孙医生'],
    anesthesiologist: ['周医生', '吴医生', '郑医生']
  });
  const [anesthesiaOptions, setAnesthesiaOptions] = useState<string[]>(['全麻', '局麻', '腰麻', '硬膜外麻醉', '静脉麻醉', '吸入麻醉']);
  const [openDropdown, setOpenDropdown] = useState<string | null>(null);
  const [multiInputText, setMultiInputText] = useState<Record<string, string>>({});
  const [touched, setTouched] = useState<Record<string, boolean>>({});
  const [formFields, setFormFields] = useState<FormField[]>([]);
  const [imagePickerOpen, setImagePickerOpen] = useState(false);
  const [imagePickerTarget, setImagePickerTarget] = useState<HTMLElement | null>(null);
  const [imageAssets, setImageAssets] = useState<{id: string; name: string; dataUrl: string}[]>([]);
  const [placeholderModal, setPlaceholderModal] = useState({
    isOpen: false, width: '200', height: '200', mode: 'frame' as 'frame' | 'manual'
  });
  const [tableModal, setTableModal] = useState({
    isOpen: false, rows: '2', cols: '3'
  });

  const editorRef = useRef<HTMLDivElement>(null);
  const videoRef = useRef<HTMLVideoElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const videoInputRef = useRef<HTMLInputElement>(null);
  const contentLoadedRef = useRef(false);
  const contentRef = useRef('');
  const stateRef = useRef({ reportData, videos, capturedFrames, activeTab, loadedTemplateId, chatMessages, chatInput });
  const videoUploadPromisesRef = useRef<Promise<void>[]>([]);
  const frameUploadPromisesRef = useRef<Promise<void>[]>([]);

  const draftKey = currentUser ? `reportEditorDraft_${currentUser.username}` : '';

  const updatePageHeight = () => {
    if (!editorRef.current) return;
    const contentHeight = editorRef.current.scrollHeight;
    const pageHeightMm = 297;
    const mmToPx = 3.7795275591;
    const pages = Math.max(2, Math.ceil(contentHeight / (pageHeightMm * mmToPx)));
    editorRef.current.style.minHeight = `${pages * pageHeightMm}mm`;
  };

  const saveDraftToStorage = React.useCallback(() => {
    const user = storage.get<User | null>('currentUser', null);
    const key = user ? `reportEditorDraft_${user.username}` : '';
    if (key) {
      const currentContent = contentRef.current || editorRef.current?.innerHTML || '';
      storage.set(key, {
        content: currentContent,
        draftReportId: reportId || null,
        reportData: stateRef.current.reportData,
        videos: stateRef.current.videos,
        capturedFrames: stateRef.current.capturedFrames,
        activeTab: stateRef.current.activeTab,
        loadedTemplateId: stateRef.current.loadedTemplateId,
        chatMessages: stateRef.current.chatMessages,
        chatInput: stateRef.current.chatInput
      });
    }
  }, [reportId]);

  const fileToDataUrl = (file: File) => new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result || ''));
    reader.onerror = () => reject(reader.error || new Error('文件读取失败'));
    reader.readAsDataURL(file);
  });

  const replaceEditorImageSrc = (from: string, to: string) => {
    if (!editorRef.current || !from || !to || from === to) return;
    editorRef.current.querySelectorAll('img').forEach((img) => {
      if ((img as HTMLImageElement).src === from || img.getAttribute('src') === from) {
        img.setAttribute('src', to);
      }
    });
    contentRef.current = editorRef.current.innerHTML;
    saveDraftToStorage();
  };

  const startVideoUpload = (videoId: string, file: File) => {
    const promise = fileToDataUrl(file)
      .then((dataUrl) => uploadFileResource(dataUrl, {
        kind: 'VIDEO',
        filename: file.name,
        reportId: reportId || undefined,
      }))
      .then((uploaded) => {
        const next = stateRef.current.videos.map((video) =>
          video.id === videoId ? { ...video, url: uploaded.url, fileId: uploaded.id } : video,
        );
        stateRef.current = { ...stateRef.current, videos: next };
        setVideos(next);
      })
      .catch(() => {});
    videoUploadPromisesRef.current.push(promise);
  };

  const startFrameUpload = (frameId: number, dataUrl: string) => {
    const promise = uploadFileResource(dataUrl, {
      kind: 'FRAME',
      filename: `frame-${frameId}.jpg`,
      reportId: reportId || undefined,
    })
      .then((uploaded) => {
        const next = stateRef.current.capturedFrames.map((frame) =>
          frame.id === frameId ? { ...frame, dataUrl: uploaded.url, fileId: uploaded.id } : frame,
        );
        stateRef.current = { ...stateRef.current, capturedFrames: next };
        setCapturedFrames(next);
        replaceEditorImageSrc(dataUrl, uploaded.url);
      })
      .catch(() => {});
    frameUploadPromisesRef.current.push(promise);
  };

  const waitForMediaUploads = async () => {
    const uploads = [...videoUploadPromisesRef.current, ...frameUploadPromisesRef.current];
    if (uploads.length === 0) return;
    await Promise.allSettled(uploads);
    videoUploadPromisesRef.current = [];
    frameUploadPromisesRef.current = [];
  };

  useEffect(() => {
    const user = storage.get<User | null>('currentUser', null);
    if (!user) { navigate('/'); return; }
    setCurrentUser(user);

    const savedMulti = storage.get<Record<string, string[]> | null>('multiSelectOptions', null);
    if (savedMulti) setMultiSelectOptions(savedMulti);
    const savedAnesthesia = storage.get<string[] | null>('anesthesiaOptions', null);
    if (savedAnesthesia) setAnesthesiaOptions(savedAnesthesia);

    const savedFields = storage.get<FormField[]>('formFieldsConfig', []);
    if (savedFields.length > 0) {
      setFormFields(savedFields);
    } else {
      setFormFields(DEFAULT_FORM_FIELDS);
      storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
    }

    const savedAssets = storage.get<{id: string; name: string; dataUrl: string}[]>('imageAssets', []);
    setImageAssets(savedAssets);

    void getFieldLibrary().then((library) => {
      if (library.formFields.length > 0) {
        setFormFields(library.formFields);
        storage.set('formFieldsConfig', library.formFields);
      }
      if (Object.keys(library.multiSelectOptions || {}).length > 0) {
        setMultiSelectOptions(library.multiSelectOptions);
        storage.set('multiSelectOptions', library.multiSelectOptions);
      }
      if (library.anesthesiaOptions.length > 0) {
        setAnesthesiaOptions(library.anesthesiaOptions);
        storage.set('anesthesiaOptions', library.anesthesiaOptions);
      }
    }).catch(() => {});

    void listFiles('TEMPLATE_ASSET').then((files) => {
      if (files.length === 0) return;
      const assets = files.map((file) => ({ id: file.id, name: file.filename, dataUrl: file.url }));
      setImageAssets(assets);
      storage.set('imageAssets', assets);
    }).catch(() => {});

    void getSystemSettings().then((apiSettings) => {
      storage.set('systemSettings', apiSettings);
    }).catch(() => {});

    const allTemplates = storage.get<Template[]>('templates', []);
    const filteredTemplates = getUsableTemplates(user, allTemplates);
    setTemplates(filteredTemplates);
    void listTemplates('use').then((response) => {
      if (response.items.length === 0) return;
      setTemplates(response.items);
      storage.set('templates', response.items);
    }).catch(() => {});

    if (reportId) {
      const draft = storage.get<Record<string, any> | null>(draftKey, null);
      if (draft && draft.draftReportId === reportId) {
        if (draft.reportData) setReportData(draft.reportData);
        if (draft.videos) {
          setVideos(draft.videos);
          if (draft.videos.length > 0) setCurrentVideoIndex(0);
          prevVideoCountRef.current = draft.videos.length;
        }
        if (draft.capturedFrames) {
          setCapturedFrames(draft.capturedFrames.sort((a: CapturedFrame, b: CapturedFrame) => a.time - b.time));
        }
        if (draft.activeTab) setActiveTab(draft.activeTab);
        if (draft.chatMessages) setChatMessages(draft.chatMessages);
        if (typeof draft.chatInput === 'string') setChatInput(draft.chatInput);
        stateRef.current = {
          ...stateRef.current,
          reportData: draft.reportData,
          videos: draft.videos,
          capturedFrames: draft.capturedFrames,
          loadedTemplateId: draft.loadedTemplateId || '',
          chatMessages: draft.chatMessages || [],
          chatInput: draft.chatInput || ''
        };
        if (editorRef.current && typeof draft.content === 'string' && draft.content.trim().length > 0) {
          editorRef.current.innerHTML = draft.content;
          contentRef.current = draft.content;
          contentLoadedRef.current = true;
          setLoadedTemplateId(draft.loadedTemplateId || '');
          setTimeout(() => updatePageHeight(), 0);
        }
      } else {
        const reports = storage.get<Report[]>('reports', []);
        const found = reports.find(r => r.id === reportId);
        if (found) {
          const users = storage.get<User[]>('users', []);
          if (!canEditReport(user, found, users)) {
            alert('您没有权限编辑此报告');
            navigate('/report-manage');
            return;
          }
          setReportData(found);
          stateRef.current = {
            ...stateRef.current,
            reportData: found,
            videos: found.videos || [],
            capturedFrames: found.capturedFrames || []
          };
          if (editorRef.current) {
            const restoreContent = storage.getSession<string | null>(`restore_${reportId}`, null);
            if (restoreFlag && restoreContent) {
              editorRef.current.innerHTML = restoreContent;
              storage.removeSession(`restore_${reportId}`);
            } else {
              editorRef.current.innerHTML = found.content;
              contentRef.current = found.content;
            }
            contentLoadedRef.current = true;
            setTimeout(() => updatePageHeight(), 0);
          }
          if (found.capturedFrames) {
            setCapturedFrames(found.capturedFrames.sort((a, b) => a.time - b.time));
          }
          if (found.videos) {
            setVideos(found.videos);
            if (found.videos.length > 0) setCurrentVideoIndex(0);
            prevVideoCountRef.current = found.videos.length;
          }
        }
      }
    } else {
      const draft = storage.get<Record<string, any> | null>(draftKey, null);
      if (draft && !draft.draftReportId) {
        if (draft.reportData) setReportData(draft.reportData);
        if (draft.videos) {
          setVideos(draft.videos);
          if (draft.videos.length > 0) setCurrentVideoIndex(0);
          prevVideoCountRef.current = draft.videos.length;
        }
        if (draft.capturedFrames) {
          setCapturedFrames(draft.capturedFrames.sort((a: CapturedFrame, b: CapturedFrame) => a.time - b.time));
        }
        if (draft.activeTab) setActiveTab(draft.activeTab);
        if (draft.chatMessages) setChatMessages(draft.chatMessages);
        stateRef.current = {
          ...stateRef.current,
          reportData: draft.reportData,
          videos: draft.videos,
          capturedFrames: draft.capturedFrames,
          loadedTemplateId: draft.loadedTemplateId || '',
          chatMessages: draft.chatMessages || []
        };
        if (editorRef.current && typeof draft.content === 'string' && draft.content.trim().length > 0) {
          editorRef.current.innerHTML = draft.content;
          contentRef.current = draft.content;
          contentLoadedRef.current = true;
          setLoadedTemplateId(draft.loadedTemplateId || '');
          setTimeout(() => updatePageHeight(), 0);
        }
      }
      if (!contentLoadedRef.current && editorRef.current) {
        const settings = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
        if (settings.defaultTemplate && filteredTemplates.length > 0) {
          const tpl = filteredTemplates.find(t => t.id === settings.defaultTemplate);
          if (tpl) {
            setLoadedTemplateId(tpl.id);
            stateRef.current = { ...stateRef.current, loadedTemplateId: tpl.id };
            editorRef.current.innerHTML = tpl.content;
            contentRef.current = tpl.content;
          } else {
            editorRef.current.innerHTML = defaultReportContent;
            contentRef.current = defaultReportContent;
          }
        } else {
          editorRef.current.innerHTML = defaultReportContent;
          contentRef.current = defaultReportContent;
        }
        contentLoadedRef.current = true;
        setTimeout(() => updatePageHeight(), 0);
      }
    }
  }, [reportId, navigate, draftKey, restoreFlag]);

  const persistEditorFieldLibrary = (next: Partial<{
    formFields: FormField[];
    multiSelectOptions: Record<string, string[]>;
    anesthesiaOptions: string[];
  }>) => {
    void updateFieldLibrary({
      formFields,
      multiSelectOptions,
      anesthesiaOptions,
      customTimeFormats: [],
      ...next,
    }).catch(() => {});
  };

  useEffect(() => {
    const handleBeforeUnload = () => saveDraftToStorage();
    const handleVisibilityChange = () => {
      if (document.visibilityState === 'hidden') saveDraftToStorage();
    };
    window.addEventListener('beforeunload', handleBeforeUnload);
    document.addEventListener('visibilitychange', handleVisibilityChange);
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
      document.removeEventListener('visibilitychange', handleVisibilityChange);
      saveDraftToStorage();
    };
  }, [saveDraftToStorage]);

  useEffect(() => {
    if (!reportId || !currentUser) return;
    const draft = storage.get<Record<string, any> | null>(draftKey, null);
    if (draft && draft.draftReportId === reportId) return;

    const loadApiReport = async () => {
      try {
        const apiReport = await getReport(reportId);
        setReportData(apiReport);
        setCapturedFrames((apiReport.capturedFrames || []).sort((a, b) => a.time - b.time));
        setVideos(apiReport.videos || []);
        if (apiReport.videos && apiReport.videos.length > 0) setCurrentVideoIndex(0);
        prevVideoCountRef.current = apiReport.videos?.length || 0;
        stateRef.current = {
          ...stateRef.current,
          reportData: apiReport,
          videos: apiReport.videos || [],
          capturedFrames: apiReport.capturedFrames || []
        };

        const reports = storage.get<Report[]>('reports', []);
        storage.set('reports', [...reports.filter(r => r.id !== apiReport.id), apiReport]);

        if (editorRef.current && !restoreFlag) {
          editorRef.current.innerHTML = apiReport.content;
          contentRef.current = apiReport.content;
          contentLoadedRef.current = true;
          setTimeout(() => updatePageHeight(), 0);
        }
      } catch {
        if (!isLocalFallbackEnabled()) {
          alert('报告加载失败或无权访问');
          navigate('/report-manage');
        }
      }
    };

    void loadApiReport();
  }, [currentUser, draftKey, reportId, restoreFlag]);

  // Auto-fill current time for fields with timeDefault === 'current'
  useEffect(() => {
    if (formFields.length === 0) return;
    let hasChange = false;
    const updates: any = {};
    formFields.forEach(field => {
      if (field.timeDefault === 'current') {
        if (field.type === 'date') {
          const current = new Date().toISOString().split('T')[0];
          if (!(reportData as any)[field.key]) {
            updates[field.key] = current;
            hasChange = true;
          }
        } else if (field.type === 'time') {
          const now = new Date();
          const hh = String(now.getHours()).padStart(2, '0');
          const mm = String(now.getMinutes()).padStart(2, '0');
          if (field.key === 'startTime') {
            if (!reportData.startHour) {
              updates.startHour = hh;
              updates.startMinute = mm;
              hasChange = true;
            }
          } else if (field.key === 'endTime') {
            if (!reportData.endHour) {
              updates.endHour = hh;
              updates.endMinute = mm;
              hasChange = true;
            }
          } else {
            if (!(reportData as any)[field.key]) {
              updates[field.key] = `${hh}:${mm}`;
              hasChange = true;
            }
          }
        }
      } else if (field.timeDefault === 'specific' && field.fixedTimeValue) {
        if (field.type === 'date') {
          if (!(reportData as any)[field.key]) {
            updates[field.key] = field.fixedTimeValue;
            hasChange = true;
          }
        } else if (field.type === 'time') {
          if (field.key === 'startTime') {
            if (!reportData.startHour) {
              const [hh, mm] = field.fixedTimeValue.split(':');
              updates.startHour = hh || '';
              updates.startMinute = mm || '';
              hasChange = true;
            }
          } else if (field.key === 'endTime') {
            if (!reportData.endHour) {
              const [hh, mm] = field.fixedTimeValue.split(':');
              updates.endHour = hh || '';
              updates.endMinute = mm || '';
              hasChange = true;
            }
          } else {
            if (!(reportData as any)[field.key]) {
              updates[field.key] = field.fixedTimeValue;
              hasChange = true;
            }
          }
        }
      }
    });
    if (hasChange) {
      setReportData(prev => {
        const next = { ...prev, ...updates };
        stateRef.current = { ...stateRef.current, reportData: next };
        return next;
      });
    }
  }, [formFields]);

  useEffect(() => {
    if (!editorRef.current) return;
    const observer = new MutationObserver(() => {
      updatePageHeight();
    });
    observer.observe(editorRef.current, { childList: true, subtree: true, attributes: true, characterData: true });
    return () => observer.disconnect();
  }, [currentUser]);

  const triggerPlaceholderUpload = (placeholder: HTMLElement) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.onchange = (ev) => {
      const file = (ev.target as HTMLInputElement).files?.[0];
      if (file) {
        const reader = new FileReader();
        reader.onload = (event) => {
          const src = event.target?.result as string;
          const mw = placeholder.style.maxWidth || placeholder.style.width || '200px';
          const mh = placeholder.style.maxHeight || placeholder.style.height || '200px';
          placeholder.innerHTML = `
            <span class="delete-btn" contenteditable="false">×</span>
            <img src="${src}" style="max-width:${mw};max-height:${mh};display:block;object-fit:contain;object-position:left top;" draggable="false">
          `;
          placeholder.classList.add('has-image');
          placeholder.style.border = 'none';
          placeholder.style.background = 'transparent';
          placeholder.style.width = 'auto';
          placeholder.style.height = 'auto';
          placeholder.style.lineHeight = 'normal';
          placeholder.style.maxWidth = mw;
          placeholder.style.maxHeight = mh;
          placeholder.style.textAlign = 'left';
          placeholder.style.verticalAlign = 'top';
          placeholder.style.justifyContent = 'flex-start';
          placeholder.style.alignItems = 'flex-start';
          if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
          saveDraftToStorage();
        };
        reader.readAsDataURL(file);
      }
    };
    input.click();
  };

  // Handle image placeholder interactions via click capture for reliable contenteditable behavior
  useEffect(() => {
    const handleEditorClick = (e: MouseEvent) => {
      // e.target may be a text node; safely resolve to an Element
      let node: Node | null = e.target as Node;
      if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
      const targetEl = node as HTMLElement | null;
      if (!targetEl) return;

      // Handle click on field-value: switch to info tab, highlight and focus corresponding input
      const fieldValue = targetEl.closest('.field-value') as HTMLElement | null;
      if (fieldValue) {
        const bindKey = fieldValue.getAttribute('data-bind');
        if (bindKey) {
          setActiveTab('info');
          stateRef.current = { ...stateRef.current, activeTab: 'info' };
          setActiveFieldKey(bindKey);
          setTimeout(() => {
            const inputEl = document.getElementById(`input-${bindKey}`);
            if (inputEl) {
              inputEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
              const focusable = inputEl.querySelector('input, select') as HTMLElement | null;
              if (focusable) {
                focusable.focus();
              }
            }
          }, 100);
        }
        return;
      }

      // 点击空白处清除高亮
      setActiveFieldKey(null);

      const placeholder = targetEl.closest('.image-placeholder') as HTMLElement | null;
      if (!placeholder) return;

      if (targetEl.closest('.delete-btn')) {
        e.stopPropagation();
        e.preventDefault();
        if (placeholder.classList.contains('has-image')) {
          placeholder.classList.remove('has-image');
          const w = parseInt(placeholder.style.maxWidth || placeholder.style.width || '0');
          const text = w > 0 && w < 80 ? '插图' : '插入/点击放置图片';
          placeholder.innerHTML = `
            <span class="delete-btn" contenteditable="false">×</span>
            <span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);display:block;width:100%;text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span>
          `;
          placeholder.style.border = '1px dashed #cbd5e1';
          placeholder.style.background = '#f8fafc';
          const mw = placeholder.style.maxWidth;
          const mh = placeholder.style.maxHeight;
          if (mw) placeholder.style.width = mw;
          if (mh) {
            placeholder.style.height = mh;
            placeholder.style.lineHeight = mh;
          }
          placeholder.style.textAlign = 'center';
          placeholder.style.verticalAlign = 'middle';
          placeholder.style.justifyContent = 'center';
          placeholder.style.alignItems = 'center';
          if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
          saveDraftToStorage();
        } else {
          const range = document.createRange();
          range.selectNode(placeholder);
          const sel = window.getSelection();
          sel?.removeAllRanges();
          sel?.addRange(range);
          document.execCommand('delete');
          if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
          saveDraftToStorage();
        }
        return;
      }

      if (!placeholder.classList.contains('has-image')) {
        e.preventDefault();
        e.stopPropagation();
        setImagePickerTarget(placeholder);
        setImagePickerOpen(true);
      }
    };

    const editor = editorRef.current;
    if (editor) {
      editor.addEventListener('click', handleEditorClick, true);
    }
    return () => {
      if (editor) {
        editor.removeEventListener('click', handleEditorClick, true);
      }
    };
  }, [saveDraftToStorage, currentUser]);

  // Prevent backspace from deleting image-placeholder elements
  useEffect(() => {
    const editor = editorRef.current;
    if (!editor) return;

    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key !== 'Backspace' && e.key !== 'Delete') return;
      const sel = window.getSelection();
      if (!sel || sel.rangeCount === 0) return;
      const range = sel.getRangeAt(0);
      
      // If selection is inside a placeholder, or if the range intersects a placeholder
      let node: Node | null = range.commonAncestorContainer;
      if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
      const placeholder = (node as HTMLElement)?.closest('.image-placeholder');
      if (placeholder) {
        e.preventDefault();
        return;
      }
      
      // If collapsed and next/previous sibling is a placeholder
      if (range.collapsed) {
        const container = range.startContainer;
        const offset = range.startOffset;
        if (container.nodeType === Node.ELEMENT_NODE) {
          const next = (container as Element).children[offset];
          const prev = (container as Element).children[offset - 1];
          if ((e.key === 'Delete' && next?.classList?.contains('image-placeholder')) ||
              (e.key === 'Backspace' && prev?.classList?.contains('image-placeholder'))) {
            e.preventDefault();
          }
        }
      }
    };

    editor.addEventListener('keydown', handleKeyDown);
    return () => editor.removeEventListener('keydown', handleKeyDown);
  }, []);

  const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => {
    const mw = placeholder.style.maxWidth || placeholder.style.width || '200px';
    const mh = placeholder.style.maxHeight || placeholder.style.height || '200px';
    placeholder.innerHTML = `
      <span class="delete-btn" contenteditable="false">×</span>
      <img src="${src}" style="max-width:${mw};max-height:${mh};display:block;object-fit:contain;object-position:left top;" draggable="false">
    `;
    placeholder.classList.add('has-image');
    placeholder.style.border = 'none';
    placeholder.style.background = 'transparent';
    placeholder.style.width = 'auto';
    placeholder.style.height = 'auto';
    placeholder.style.lineHeight = 'normal';
    placeholder.style.maxWidth = mw;
    placeholder.style.maxHeight = mh;
    placeholder.style.textAlign = 'left';
    placeholder.style.verticalAlign = 'top';
    placeholder.style.justifyContent = 'flex-start';
    placeholder.style.alignItems = 'flex-start';
    if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
    saveDraftToStorage();
  };

  const execCmd = (command: string, value: string | undefined = undefined) => {
    editorRef.current?.focus();
    document.execCommand(command, false, value);
    editorRef.current?.focus();
    if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
    saveDraftToStorage();
  };

  const changeLineHeight = (height: string) => {
    const sel = window.getSelection();
    if (!sel || !sel.rangeCount) return;
    let node = sel.getRangeAt(0).commonAncestorContainer;
    if (node.nodeType === Node.TEXT_NODE) node = node.parentNode as Node;
    const block = (node as HTMLElement).closest('p, div, td, h1, h2, h3, li');
    if (block) {
      (block as HTMLElement).style.lineHeight = height;
      if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
      saveDraftToStorage();
    }
  };

  const changeAlignment = (align: 'left' | 'center' | 'right' | 'justify') => {
    const sel = window.getSelection();
    if (!sel || !sel.rangeCount) return;
    let node = sel.getRangeAt(0).commonAncestorContainer;
    if (node.nodeType === Node.TEXT_NODE) node = node.parentNode as Node;
    const block = (node as HTMLElement).closest('p, div, td, h1, h2, h3, li');
    if (block) {
      (block as HTMLElement).style.textAlign = align;
      if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
      saveDraftToStorage();
    }
  };

  const insertTable = () => {
    editorRef.current?.focus();
    setTableModal({ isOpen: true, rows: '2', cols: '3' });
  };

  const insertImage = () => {
    editorRef.current?.focus();
    setPlaceholderModal({ isOpen: true, width: '200', height: '200', mode: 'frame' });
  };

  const insertAiRegion = () => {
    const name = window.prompt('请输入 AI 可编辑区域的名称（如：手术步骤、病灶描述）：');
    if (!name || !name.trim()) return;
    if (editorRef.current?.querySelector(`[data-ai-id="${name}"]`)) {
      window.alert('该区域名称已存在，请使用其他名称以保证 AI 定位准确。');
      return;
    }
    editorRef.current?.focus();
    const html = `<div class="ai-region" data-ai-id="${name}" data-ai-title="${name}" style="border: 1px dashed #3b82f6; padding: 16px 12px 12px; margin: 8px 0; position: relative; min-height: 60px; background: #f8fafc; border-radius: 6px;"><div contenteditable="false" style="position: absolute; top: -10px; right: 10px; background: #3b82f6; color: white; font-size: 10px; padding: 2px 8px; border-radius: 12px; z-index: 10; user-select: none; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">${name}-AI可编辑区域</div><div class="ai-content" style="min-height: 20px;">&#8203;</div></div><p><br></p>`;
    document.execCommand('insertHTML', false, html);
    if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
    saveDraftToStorage();
  };

  const handleVideoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files || []) as File[];
    const newVideos = files.map(file => ({
      id: Math.random().toString(36).substr(2, 9),
      name: file.name,
      url: URL.createObjectURL(file),
      duration: 0
    }));
    const combined = [...videos, ...newVideos];
    setVideos(combined);
    stateRef.current = { ...stateRef.current, videos: combined };
    newVideos.forEach((video, index) => startVideoUpload(video.id, files[index]));
    setCurrentVideoIndex(videos.length); // select first newly uploaded video
    if (videoInputRef.current) videoInputRef.current.value = '';
    saveDraftToStorage();
  };

  const removeVideo = (id: string) => {
    const idx = videos.findIndex(v => v.id === id);
    const updated = videos.filter(v => v.id !== id);
    setVideos(updated);
    stateRef.current = { ...stateRef.current, videos: updated };
    if (currentVideoIndex >= updated.length) {
      setCurrentVideoIndex(updated.length > 0 ? 0 : -1);
    } else if (currentVideoIndex === idx && updated.length > 0) {
      setCurrentVideoIndex(0);
    }
    const nextFrames = capturedFrames.filter(f => f.videoIndex !== idx).map(f => {
      if (f.videoIndex > idx) return { ...f, videoIndex: f.videoIndex - 1 };
      return f;
    }).sort((a, b) => a.time - b.time);
    setCapturedFrames(nextFrames);
    stateRef.current = { ...stateRef.current, capturedFrames: nextFrames };
    saveDraftToStorage();
  };

  const selectVideo = (index: number) => {
    setCurrentVideoIndex(index);
    setIsPlaying(false);
  };

  const togglePlay = () => {
    if (!videoRef.current) return;
    if (isPlaying) videoRef.current.pause();
    else videoRef.current.play();
    setIsPlaying(!isPlaying);
  };

  const captureFrame = () => {
    if (!videoRef.current || !canvasRef.current || currentVideoIndex === -1) return;
    const video = videoRef.current;
    const canvas = canvasRef.current;
    const MAX_WIDTH = 800;
    const scale = Math.min(1, MAX_WIDTH / video.videoWidth);
    canvas.width = video.videoWidth * scale;
    canvas.height = video.videoHeight * scale;
    const ctx = canvas.getContext('2d');
    ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);

    const newFrame: CapturedFrame = {
      id: Date.now(),
      videoIndex: currentVideoIndex,
      videoName: videos[currentVideoIndex].name,
      time: video.currentTime,
      timeFormatted: formatTime(video.currentTime),
      dataUrl: canvas.toDataURL('image/jpeg', 0.6),
      isManual: true
    };
    const nextFrames = [...capturedFrames, newFrame].sort((a, b) => a.time - b.time);
    setCapturedFrames(nextFrames);
    stateRef.current = { ...stateRef.current, capturedFrames: nextFrames };
    startFrameUpload(newFrame.id, newFrame.dataUrl);
    saveDraftToStorage();
  };

  const autoCaptureFrames = async () => {
    if (!videoRef.current || currentVideoIndex === -1) return;
    const video = videoRef.current;
    const settings = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
    const positions = settings.framePositions || [7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6];
    const dur = video.duration || 1;

    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const wasPlaying = !video.paused;
    if (wasPlaying) video.pause();

    let accumulatedFrames = [...capturedFrames];
    for (let i = 0; i < positions.length; i++) {
      const pos = positions[i];
      const time = (pos / 100) * dur;
      video.currentTime = time;
      await new Promise<void>(resolve => {
        const onSeeked = () => {
          video.removeEventListener('seeked', onSeeked);
          resolve();
        };
        video.addEventListener('seeked', onSeeked);
      });
      const MAX_WIDTH = 800;
      const scale = Math.min(1, MAX_WIDTH / video.videoWidth);
      canvas.width = video.videoWidth * scale;
      canvas.height = video.videoHeight * scale;
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      const newFrame: CapturedFrame = {
        id: Date.now() + Math.random(),
        videoIndex: currentVideoIndex,
        videoName: videos[currentVideoIndex].name,
        time,
        timeFormatted: formatTime(time),
        dataUrl: canvas.toDataURL('image/jpeg', 0.6),
        isManual: false
      };
      accumulatedFrames = [...accumulatedFrames, newFrame].sort((a, b) => a.time - b.time);
      flushSync(() => {
        setCapturedFrames(accumulatedFrames);
      });
      stateRef.current = { ...stateRef.current, capturedFrames: accumulatedFrames };
      startFrameUpload(newFrame.id, newFrame.dataUrl);
      if (settings.autoInsertFrames && settings.autoInsertFrameIndices?.includes(i)) {
        const baseDelay = (settings.autoInsertDelay || 0) * 1000;
        const insertOrderIndex = settings.autoInsertFrameIndices.indexOf(i);
        const actualDelay = baseDelay > 0 ? baseDelay * (insertOrderIndex + 1) : 0;

        setTimeout(() => {
          if (!editorRef.current) return;
          const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image):not([data-mode="manual"])') as HTMLElement | null;
          if (emptyPlaceholder) {
            emptyPlaceholder.innerHTML = `
              <span class="delete-btn" contenteditable="false">×</span>
              <img src="${newFrame.dataUrl}" style="max-width:${emptyPlaceholder.style.maxWidth || emptyPlaceholder.style.width || '200px'};max-height:${emptyPlaceholder.style.maxHeight || emptyPlaceholder.style.height || '200px'};display:block;object-fit:contain;object-position:left top;" draggable="false">
            `;
            emptyPlaceholder.classList.add('has-image');
            emptyPlaceholder.style.border = 'none';
            emptyPlaceholder.style.background = 'transparent';
            emptyPlaceholder.style.width = 'auto';
            emptyPlaceholder.style.height = 'auto';
            emptyPlaceholder.style.lineHeight = 'normal';
            emptyPlaceholder.style.maxWidth = emptyPlaceholder.style.maxWidth || emptyPlaceholder.style.width || '200px';
            emptyPlaceholder.style.maxHeight = emptyPlaceholder.style.maxHeight || emptyPlaceholder.style.height || '200px';
            emptyPlaceholder.style.textAlign = 'left';
            emptyPlaceholder.style.verticalAlign = 'top';
            emptyPlaceholder.style.justifyContent = 'flex-start';
            emptyPlaceholder.style.alignItems = 'flex-start';
            contentRef.current = editorRef.current.innerHTML;
            saveDraftToStorage();
          }
        }, actualDelay);
      }
    }
    if (settings.autoInsertFrames && editorRef.current) {
      contentRef.current = editorRef.current.innerHTML;
    }
    if (wasPlaying) video.play();
    saveDraftToStorage();
  };

  const formatTime = (seconds: number) => {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  };

  const checkAiRegions = () => {
    if (!editorRef.current) return [];
    return Array.from(editorRef.current.querySelectorAll('.ai-region')).map((el) => {
      const id = (el as HTMLElement).getAttribute('data-ai-id') || '';
      const title = (el as HTMLElement).getAttribute('data-ai-title') || id;
      return { id, title };
    }).filter(r => r.id);
  };

  const stripHtml = (html: string): string => {
    const tmp = document.createElement('div');
    tmp.innerHTML = html
      .replace(/<br\s*\/?>/gi, '\n')
      .replace(/<\/p>\s*<p/gi, '</p>\n\n<p');
    return (tmp.innerText || tmp.textContent || '').trim();
  };

  const computeDiffHtml = (oldText: string, newText: string, side: 'left' | 'right'): string => {
    const diffs = diffChars(oldText, newText);
    let html = '';
    for (const part of diffs) {
      let value = part.value.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
      if (side === 'left' && part.removed) {
        html += `<span class="diff-removed" style="background-color:#fee2e2;color:#dc2626;text-decoration:line-through;">${value}</span>`;
      } else if (side === 'right' && part.added) {
        html += `<span class="diff-added" style="background-color:#dcfce7;color:#16a34a;font-weight:500;">${value}</span>`;
      } else if (!part.added && !part.removed) {
        html += value;
      }
    }
    return html;
  };

  function floatTo16BitPCM(input: Float32Array): ArrayBuffer {
    const output = new DataView(new ArrayBuffer(input.length * 2));
    for (let i = 0; i < input.length; i++) {
      const s = Math.max(-1, Math.min(1, input[i]));
      output.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
    }
    return output.buffer;
  }

  function arrayBufferToBase64(buffer: ArrayBuffer): string {
    const bytes = new Uint8Array(buffer);
    let binary = '';
    for (let i = 0; i < bytes.byteLength; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
  }

  const toggleListening = async () => {
    // 专门提取一个彻底关闭物理麦克风的函数
    const stopMicrophone = () => {
      if (xfAudioContextRef.current) {
        try { xfAudioContextRef.current.close(); } catch {}
        xfAudioContextRef.current = null;
      }
      if (xfMediaStreamRef.current) {
        xfMediaStreamRef.current.getTracks().forEach(t => t.stop());
        xfMediaStreamRef.current = null;
      }
    };

    if (isListening) {
      setIsListening(false);
      stopMicrophone();

      if (xfWsRef.current && xfWsRef.current.readyState === WebSocket.OPEN) {
        try {
          const endFrame = { data: { status: 2, format: 'audio/L16;rate=16000', encoding: 'raw', audio: '' } };
          xfWsRef.current.send(JSON.stringify(endFrame));
        } catch {}
      }
      return;
    }

    try {
      const mediaDevices = navigator.mediaDevices;
      const AudioContextClass = window.AudioContext || (window as AudioWindow).webkitAudioContext;
      if (!mediaDevices?.getUserMedia) {
        alert(window.isSecureContext
          ? '当前浏览器不支持麦克风采集，请更换新版 Chrome/Edge 后重试。'
          : '浏览器不允许在普通局域网 HTTP 页面中调用麦克风。请使用 http://localhost:4002、https://localhost:4443，或用浏览器演示参数把当前 HTTP 地址标记为可信。');
        return;
      }
      if (!AudioContextClass) {
        alert('当前浏览器不支持音频采集处理，请更换新版 Chrome/Edge 后重试。');
        return;
      }

      const ws = new WebSocket(getSpeechIatWebSocketUrl());
      xfWsRef.current = ws;
      let frameStatus = 0;

      ws.onopen = async () => {
        try {
          const stream = await mediaDevices.getUserMedia({ audio: true });
          xfMediaStreamRef.current = stream;
          const audioContext = new AudioContextClass({ sampleRate: 16000 });
          xfAudioContextRef.current = audioContext;
          const source = audioContext.createMediaStreamSource(stream);
          const processor = audioContext.createScriptProcessor(4096, 1, 1);

          processor.onaudioprocess = (e) => {
            if (ws.readyState !== WebSocket.OPEN || !xfAudioContextRef.current) return;
            const inputData = e.inputBuffer.getChannelData(0);
            const pcmBuffer = floatTo16BitPCM(inputData);
            const base64Audio = arrayBufferToBase64(pcmBuffer);
            const frame: any = { data: { status: frameStatus, format: 'audio/L16;rate=16000', encoding: 'raw', audio: base64Audio } };
            ws.send(JSON.stringify(frame));
            frameStatus = 1;
          };

          source.connect(processor);
          processor.connect(audioContext.destination);
          setIsListening(true);
        } catch (e: any) {
          alert('麦克风启动失败: ' + e.message);
          setIsListening(false);
          ws.close();
        }
      };

      ws.onmessage = (event) => {
        try {
          const jsonData = JSON.parse(event.data);
          if (jsonData.code !== 0 && jsonData.code !== undefined) {
            alert(`讯飞语音报错: ${jsonData.message || '未知错误'} (错误码: ${jsonData.code})`);
            setIsListening(false);
            stopMicrophone();
            ws.close();
            return;
          }
          if (jsonData.data?.result?.ws) {
            let seg = '';
            for (const w of jsonData.data.result.ws) { if (w.cw?.[0]?.w) seg += w.cw[0].w; }
            if (seg) {
              setChatInput(prev => prev + seg);
            }
          }
          if (jsonData.data?.status === 2) {
            ws.close();
            xfWsRef.current = null;
            setIsListening(false);
            stopMicrophone();
          }
        } catch {}
      };

      ws.onerror = () => { alert('讯飞语音连接失败，请确认已登录且超级管理员已配置语音参数'); setIsListening(false); stopMicrophone(); };
      ws.onclose = () => { setIsListening(false); stopMicrophone(); };
    } catch (e: any) {
      alert('讯飞语音初始化失败: ' + e.message);
    }
  };

  const handleAIGenerate = async (text: string) => {
    if (!text.trim()) return;
    const userMsgId = Date.now().toString();
    const selectedEditorImageUrls = editorImages.filter(img => aiSelectedEditorImages.includes(img.id)).map(img => img.src);
    const allImages = [...selectedEditorImageUrls, ...aiUploadedImages.map(i => i.dataUrl)];
    setChatMessages(prev => [...prev, { id: userMsgId, role: 'user', content: text, images: allImages.length > 0 ? allImages : undefined }]);
    setChatInput('');
    setIsGenerating(true);
    try {
      const settings = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
      const provider = settings.aiProviders?.[settings.activeAiProvider || 'kimi'];
      const modelName = provider?.modelName || 'moonshot-v1-32k-vision-preview';
      let actualTargetId = aiTargetRegion;
      if (aiModifyEnabled && actualTargetId === 'none') {
        const availableRegions = checkAiRegions();
        if (availableRegions.length > 0) {
          actualTargetId = availableRegions[0].id;
          setAiTargetRegion(actualTargetId);
        }
      }
      const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"] .ai-content`) as HTMLElement | null;
      // 合并溢出的段落：浏览器 contentEditable 可能在回车时把 <p> 生成到 .ai-content 之外
      const aiRegion = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"]`);
      if (aiRegion && targetRegionEl) {
        let nextSibling = targetRegionEl.nextElementSibling;
        while (nextSibling) {
          const toMove = nextSibling;
          nextSibling = nextSibling.nextElementSibling;
          if (toMove.tagName === 'P') {
            targetRegionEl.appendChild(toMove);
          }
        }
        // 同步更新 contentRef 和草稿
        if (editorRef.current) {
          contentRef.current = editorRef.current.innerHTML;
          saveDraftToStorage();
        }
      }
      const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/&#8203;/g, '').replace(/>(\s+)</g, '><').trim() : '';
      const globalContextText = editorRef.current?.innerText || '';
      let messageContent: any;
      const selectedEditorImageUrls = editorImages.filter(img => aiSelectedEditorImages.includes(img.id)).map(img => img.src);
      const allImages = [...selectedEditorImageUrls, ...aiUploadedImages.map(i => i.dataUrl)];
      let promptText = `【全局手术报告参考内容】:\n${globalContextText}\n\n`;
      if (aiModifyEnabled && targetRegionEl) {
        promptText += `【你需要进行修改的目标区域 HTML 源码】:\n${currentHtml || '（当前区域为空）'}\n\n`;
      }
      promptText += `【医生指令】: ${text}\n\n【格式要求】:\n1. 仅针对【目标区域】的主题生成对应的多段落 HTML 内容\n2. ⚠️ 绝对禁止将【全局参考内容】中的其他模块（如：基本信息、术后情况、标本描述、病理结果、医生签名、日期等）生成并混入你的输出中！你只能输出该区域应有的内容\n3. 段落使用 <p> 标签，段落之间不要使用 <br> 标签或换行符\n4. 输出紧凑 HTML，标签间不要有空格或换行`;
      if (allImages.length > 0) {
        messageContent = [];
        allImages.forEach(url => {
          messageContent.push({ type: 'image_url', image_url: { url } });
        });
        messageContent.push({ type: 'text', text: promptText });
      } else {
        messageContent = promptText;
      }
      const systemPrompt = aiModifyEnabled
        ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识，以及你需要修改的【目标区域 HTML 源码】。\n请根据用户的【医生指令】，直接重写并输出目标区域的 HTML。\n\n重要指令：\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"（简短回复）和 "updatedHtml"（修改后的完整 HTML 代码）两个字段\n3. 【内容边界】：全局参考内容仅供你理解上下文。你的 updatedHtml 只能包含目标区域本身的内容（例如：如果目标区域是"手术步骤"，你就只写步骤）。严禁输出签名、落款、术后总结等属于报告其他部分的结构！\n4. 段落必须使用 <p> 标签包裹，段落间绝对不要使用 <br> 标签，也不要使用换行符 (\\n)\n5. 输出的 HTML 必须紧凑，标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记（如 ```json）'
        : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】，并根据【医生指令】进行专业解答。\n重要指令：\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"（你的专业回答）一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记\n5. 无论图片内容是什么，请严格以纯 JSON 格式输出，禁止任何多余的开头解释';
      const payload: any = {
        model: modelName,
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: messageContent }
        ],
        temperature: 0.3
      };
      const isKimiK25 = settings.activeAiProvider === 'kimi' && /k2\.5/i.test(modelName);
      if (isKimiK25) {
        delete payload.temperature;
        delete payload.top_p;
        delete payload.presence_penalty;
        delete payload.frequency_penalty;
      }
      const logEntry = {
        startTime: new Date().toISOString(),
        modelConfig: { provider: settings.activeAiProvider || 'kimi', endpoint: '/api/ai/chat', modelName },
        requestPayload: JSON.parse(JSON.stringify(payload)),
        responsePayload: null as any | null,
        errorDetail: null as { status: number; statusText: string; responseText: string; message: string } | null
      };
      const data = await createAiChatCompletion(payload);
      logEntry.responsePayload = data;
      setLastExchangeLog(logEntry);
      const responseText = data.choices[0].message.content.trim();
      const cleanedText = responseText.replace(/```json\n?|```/g, '');
      let responseJson: any = {};
      try {
        responseJson = JSON.parse(cleanedText);
      } catch {
        const jsonMatch = cleanedText.match(/\{[\s\S]*\}/);
        if (jsonMatch) {
          try {
            responseJson = JSON.parse(jsonMatch[0]);
          } catch {
            if (!aiModifyEnabled) responseJson = { reply: cleanedText };
            else throw new Error('AI 返回格式异常，无法解析 JSON');
          }
        } else {
          if (!aiModifyEnabled) responseJson = { reply: cleanedText };
          else throw new Error('AI 返回格式异常，无法解析 JSON');
        }
      }
      if (responseJson.reply) {
        setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: responseJson.reply }]);
      }
      if (aiModifyEnabled && !responseJson.updatedHtml) {
        setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: '【系统提示】AI 未能生成修改内容，请尝试重新描述您的需求。' }]);
      }
      if (responseJson.updatedHtml && aiModifyEnabled) {
        let cleanHtml = responseJson.updatedHtml;
        cleanHtml = cleanHtml.replace(/<br\s*\/?>/gi, '');
        cleanHtml = cleanHtml.replace(/<\/p>\s*<p>/gi, '</p><p>');
        cleanHtml = cleanHtml.trim();
        cleanHtml = cleanHtml.replace(/>(\s+)</g, '><');
        cleanHtml = cleanHtml.replace(/<p>/gi, '<p style="font-family: SimSun; font-size: 12pt; line-height: 1.5; margin: 0; padding: 0;">');
        if (targetRegionEl) {
          setDiffModal({
            isOpen: true,
            originalHtml: currentHtml,
            newHtml: cleanHtml,
            targetId: actualTargetId
          });
        } else {
          execCmd('insertHTML', cleanHtml);
        }
      }
      setAiUploadedImages([]);
      setAiSelectedEditorImages([]);
    } catch (error: any) {
      console.error(error);
      setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: `【系统错误】: ${error.message}` }]);
    } finally {
      setIsGenerating(false);
    }
  };

  const confirmAiInjection = (newHtml: string, regionId: string) => {
    if (!editorRef.current) return;
    const cleanHtml = newHtml.replace(/<span class="diff-(added|removed)"[^>]*>(.*?)<\/span>/gi, '$2');
    const targetContent = editorRef.current.querySelector(`.ai-region[data-ai-id="${regionId}"] .ai-content`) as HTMLElement;
    if (targetContent) {
      targetContent.focus();
      const sel = window.getSelection();
      const range = document.createRange();
      range.selectNodeContents(targetContent);
      sel?.removeAllRanges();
      sel?.addRange(range);
      document.execCommand('insertHTML', false, cleanHtml);
      targetContent.style.transition = 'background-color 0.3s ease';
      targetContent.style.backgroundColor = '#bfdbfe';
      setTimeout(() => {
        targetContent.style.backgroundColor = '#eff6ff';
        setTimeout(() => {
          targetContent.style.backgroundColor = 'transparent';
        }, 800);
      }, 400);
      contentRef.current = editorRef.current.innerHTML;
      saveDraftToStorage();
    }
    setDiffModal(null);
  };

  const handleDragStart = (e: React.DragEvent, frame: CapturedFrame) => {
    e.dataTransfer.setData('frameId', frame.id.toString());
  };

  const fillPlaceholder = (placeholder: HTMLElement, frame: CapturedFrame) => {
    const mw = placeholder.style.maxWidth || placeholder.style.width || '200px';
    const mh = placeholder.style.maxHeight || placeholder.style.height || '200px';
    placeholder.innerHTML = `
      <span class="delete-btn" contenteditable="false">×</span>
      <img src="${frame.dataUrl}" style="max-width:${mw};max-height:${mh};display:block;object-fit:contain;object-position:left top;" draggable="false">
    `;
    placeholder.classList.add('has-image');
    placeholder.style.border = 'none';
    placeholder.style.background = 'transparent';
    placeholder.style.width = 'auto';
    placeholder.style.height = 'auto';
    placeholder.style.lineHeight = 'normal';
    placeholder.style.maxWidth = mw;
    placeholder.style.maxHeight = mh;
    placeholder.style.textAlign = 'left';
    placeholder.style.verticalAlign = 'top';
    placeholder.style.justifyContent = 'flex-start';
    placeholder.style.alignItems = 'flex-start';
    if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
    saveDraftToStorage();
  };

  const handleDrop = (e: React.DragEvent, placeholder: HTMLElement) => {
    e.preventDefault();
    if (placeholder.getAttribute('data-mode') === 'manual') {
      alert('此处为静态图片占位符，仅支持点击插入（如Logo/签名），不支持拖入关键帧');
      return;
    }
    const frameId = e.dataTransfer.getData('frameId');
    const frame = capturedFrames.find(f => f.id.toString() === frameId);
    if (frame) {
      fillPlaceholder(placeholder, frame);
    }
  };

  const insertFrameToPlaceholder = (frame: CapturedFrame) => {
    if (!editorRef.current) {
      alert('编辑器未准备好');
      return;
    }
    const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image):not([data-mode="manual"])') as HTMLElement | null;
    if (!emptyPlaceholder) {
      alert('没有可插入图片的空位');
      return;
    }
    fillPlaceholder(emptyPlaceholder, frame);
  };

  const seekToFrame = (frame: CapturedFrame) => {
    if (!videoRef.current) return;
    if (frame.videoIndex !== currentVideoIndex) {
      setCurrentVideoIndex(frame.videoIndex);
    }
    setTimeout(() => {
      if (videoRef.current) {
        videoRef.current.currentTime = frame.time;
      }
    }, 50);
  };

  const handleFrameKeyNav = (direction: 'left' | 'right') => {
    if (!videoRef.current) return;
    const delta = direction === 'left' ? -5 : 5;
    videoRef.current.currentTime = Math.max(0, Math.min(duration, videoRef.current.currentTime + delta));
  };

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
        // Only if video tab is active or video element is focused
        if (activeTab === 'video' || document.activeElement === videoRef.current) {
          handleFrameKeyNav(e.key === 'ArrowLeft' ? 'left' : 'right');
        }
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [activeTab, duration]);

  // Auto-capture frames when new videos are uploaded
  useEffect(() => {
    if (videos.length > prevVideoCountRef.current && currentVideoIndex !== -1) {
      const timer = setTimeout(() => {
        if (videoRef.current && videoRef.current.duration) {
          autoCaptureFrames();
        } else if (videoRef.current) {
          const onMeta = () => {
            autoCaptureFrames();
            videoRef.current?.removeEventListener('loadedmetadata', onMeta);
          };
          videoRef.current.addEventListener('loadedmetadata', onMeta);
        }
      }, 300);
      return () => clearTimeout(timer);
    }
    prevVideoCountRef.current = videos.length;
  }, [videos.length, currentVideoIndex]);

  // Apply selected template content
  useEffect(() => {
    if (pendingTemplateId && editorRef.current) {
      const tpl = templates.find(t => t.id === pendingTemplateId);
      if (tpl) {
        editorRef.current.innerHTML = tpl.content;
        contentRef.current = tpl.content;
        const nextReportData: any = {
          title: tpl.name || '腹腔镜胆囊切除术报告',
          patientName: '',
          hospitalId: '',
          patientGender: '',
          patientAge: '',
          department: '',
          bedNumber: '',
          surgeryDate: '',
          startHour: '',
          startMinute: '',
          endHour: '',
          endMinute: '',
          surgeon: [],
          assistant: [],
          anesthesiologist: [],
          anesthesiaType: '',
          status: 'draft'
        };
        formFields.forEach(field => {
          if (field.category === '时间') {
            if (field.timeDefault === 'specific' && field.fixedTimeValue) {
              if (field.type === 'date') {
                nextReportData[field.key] = field.fixedTimeValue;
              } else if (field.type === 'time') {
                const [hh, mm] = field.fixedTimeValue.split(':');
                if (field.key === 'startTime') {
                  nextReportData.startHour = hh || '';
                  nextReportData.startMinute = mm || '';
                } else if (field.key === 'endTime') {
                  nextReportData.endHour = hh || '';
                  nextReportData.endMinute = mm || '';
                } else {
                  nextReportData[field.key] = field.fixedTimeValue;
                }
              }
            } else if (field.timeDefault === 'current') {
              if (field.type === 'date') {
                nextReportData[field.key] = new Date().toISOString().split('T')[0];
              } else if (field.type === 'time') {
                const now = new Date();
                const hh = String(now.getHours()).padStart(2, '0');
                const mm = String(now.getMinutes()).padStart(2, '0');
                if (field.key === 'startTime') {
                  nextReportData.startHour = hh;
                  nextReportData.startMinute = mm;
                } else if (field.key === 'endTime') {
                  nextReportData.endHour = hh;
                  nextReportData.endMinute = mm;
                } else {
                  nextReportData[field.key] = `${hh}:${mm}`;
                }
              }
            }
          }
        });
        if (!nextReportData.surgeryDate) {
          nextReportData.surgeryDate = new Date().toISOString().split('T')[0];
        }
        setLoadedTemplateId(tpl.id);
        setReportData(nextReportData);
        setVideos([]);
        setCapturedFrames([]);
        setCurrentVideoIndex(-1);
        setChatMessages([]);
        setChatInput('');
        setAiUploadedImages([]);
        setAiSelectedEditorImages([]);
        prevVideoCountRef.current = 0;
        stateRef.current = {
          ...stateRef.current,
          loadedTemplateId: tpl.id,
          reportData: nextReportData,
          videos: [],
          capturedFrames: [],
          chatMessages: [],
          chatInput: '',
          activeTab: stateRef.current.activeTab
        };
        updatePageHeight();
        saveDraftToStorage();
      }
      setPendingTemplateId(null);
    }
  }, [pendingTemplateId, templates]);

  // Safety net: ensure editor gets initialized if ref was not ready during init effect
  React.useLayoutEffect(() => {
    if (contentLoadedRef.current || !editorRef.current) return;

    const user = storage.get<User | null>('currentUser', null);
    const key = user ? `reportEditorDraft_${user.username}` : '';
    const draft = key ? storage.get<Record<string, any> | null>(key, null) : null;

    if (reportId) {
      if (draft && draft.draftReportId === reportId && typeof draft.content === 'string' && draft.content.trim().length > 0) {
        editorRef.current.innerHTML = draft.content;
        contentRef.current = draft.content;
        contentLoadedRef.current = true;
        setLoadedTemplateId(draft.loadedTemplateId || '');
        stateRef.current = {
          ...stateRef.current,
          reportData: draft.reportData,
          videos: draft.videos,
          capturedFrames: draft.capturedFrames,
          loadedTemplateId: draft.loadedTemplateId || '',
          chatMessages: draft.chatMessages || [],
          chatInput: draft.chatInput || ''
        };
        setTimeout(() => updatePageHeight(), 0);
        return;
      }
      const reports = storage.get<Report[]>('reports', []);
      const found = reports.find(r => r.id === reportId);
      if (found) {
        const users = storage.get<User[]>('users', []);
        if (!user || !canEditReport(user, found, users)) {
          alert('您没有权限编辑此报告');
          navigate('/report-manage');
          return;
        }
        const restoreContent = storage.getSession<string | null>(`restore_${reportId}`, null);
        if (restoreFlag && restoreContent) {
          editorRef.current.innerHTML = restoreContent;
          storage.removeSession(`restore_${reportId}`);
        } else {
          editorRef.current.innerHTML = found.content;
        }
        contentLoadedRef.current = true;
        stateRef.current = {
          ...stateRef.current,
          reportData: found,
          videos: found.videos || [],
          capturedFrames: found.capturedFrames || []
        };
        setTimeout(() => updatePageHeight(), 0);
        return;
      }
    } else {
      if (draft && !draft.draftReportId && typeof draft.content === 'string' && draft.content.trim().length > 0) {
        editorRef.current.innerHTML = draft.content;
        contentRef.current = draft.content;
        contentLoadedRef.current = true;
        setLoadedTemplateId(draft.loadedTemplateId || '');
        stateRef.current = {
          ...stateRef.current,
          reportData: draft.reportData,
          videos: draft.videos,
          capturedFrames: draft.capturedFrames,
          loadedTemplateId: draft.loadedTemplateId || ''
        };
        setTimeout(() => updatePageHeight(), 0);
        return;
      }
    }

    const settings = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
    const allTemplates = storage.get<Template[]>('templates', []);
    const userData = storage.get<User | null>('currentUser', null);
    const filteredTemplates = userData ? getUsableTemplates(userData, allTemplates) : [];
    if (settings.defaultTemplate && filteredTemplates.length > 0) {
      const tpl = filteredTemplates.find(t => t.id === settings.defaultTemplate);
      if (tpl) {
        setLoadedTemplateId(tpl.id);
        stateRef.current = { ...stateRef.current, loadedTemplateId: tpl.id };
        editorRef.current.innerHTML = tpl.content;
      } else {
        editorRef.current.innerHTML = defaultReportContent;
      }
    } else {
      editorRef.current.innerHTML = defaultReportContent;
    }
    contentLoadedRef.current = true;
    setTimeout(() => updatePageHeight(), 0);
  }, []);

  const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
  const hour12Options = Array.from({ length: 12 }, (_, i) => ((i + 1).toString().padStart(2, '0')));
  const minuteOptions = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));

  const formatDateDisplay = (isoDate: string, fmt?: string): string => {
    if (!isoDate || !fmt) return isoDate || '';
    const [y, m, d] = isoDate.split('-');
    return fmt
      .replace(/YYYY/g, y || '')
      .replace(/MM/g, m || '')
      .replace(/DD/g, d || '');
  };

  const formatTimeDisplay = (timeStr: string, fmt?: string): string => {
    if (!timeStr || !fmt) return timeStr || '';
    if (fmt === '24h') fmt = 'HH:mm';
    const [h24str, mstr] = timeStr.split(':');
    const h24 = parseInt(h24str) || 0;
    const isPM = h24 >= 12;
    let h12 = h24 % 12;
    if (h12 === 0) h12 = 12;
    return fmt
      .replace(/HH/g, String(h24).padStart(2, '0'))
      .replace(/mm/g, mstr || '00')
      .replace(/hh/g, String(h12).padStart(2, '0'))
      .replace(/A/g, isPM ? '下午' : '上午');
  };

  const parseDateFromFormat = (text: string, fmt?: string): string => {
    if (!text || !fmt) return text;
    const nums = text.match(/\d+/g);
    if (!nums) return text;
    let y = '', m = '', d = '';
    if (nums.length >= 3) { y = nums[0].padStart(4, '0'); m = nums[1].padStart(2, '0'); d = nums[2].padStart(2, '0'); }
    else if (nums.length === 2) { m = nums[0].padStart(2, '0'); d = nums[1].padStart(2, '0'); y = new Date().getFullYear().toString(); }
    return `${y}-${m}-${d}`;
  };

  const parseTimeFromFormat = (text: string, fmt?: string): string => {
    if (!text || !fmt) return text;
    const nums = text.match(/\d+/g);
    const ampm = text.match(/上午|下午/);
    if (!nums || nums.length < 2) return text;
    let h = parseInt(nums[0]);
    if (ampm) {
      const isPM = ampm[0] === '下午';
      if (isPM && h !== 12) h += 12;
      if (!isPM && h === 12) h = 0;
    }
    return `${String(h).padStart(2, '0')}:${nums[1].padStart(2, '0')}`;
  };

  const to24h = (h12: number, isPM: boolean): number => {
    if (isPM && h12 !== 12) return h12 + 12;
    if (!isPM && h12 === 12) return 0;
    return h12;
  };

  const from24h = (h24: number): { h: number; isPM: boolean } => {
    const isPM = h24 >= 12;
    let h = h24 % 12;
    if (h === 0) h = 12;
    return { h, isPM };
  };

  const addTag = (field: string, value: string) => {
    const current = (reportData as any)[field] || [];
    if (!current.includes(value)) {
      const next = { ...reportData, [field]: [...current, value] };
      setReportData(next);
      stateRef.current = { ...stateRef.current, reportData: next };
      saveDraftToStorage();
    }
    // Persist custom value to global options for future reuse
    const opts = multiSelectOptions[field] || [];
    if (!opts.includes(value)) {
      const next = { ...multiSelectOptions, [field]: [...opts, value] };
      setMultiSelectOptions(next);
      storage.set('multiSelectOptions', next);
      persistEditorFieldLibrary({ multiSelectOptions: next });
    }
    // Sync to formFieldsConfig
    const fieldDef = formFields.find(f => f.key === field);
    if (fieldDef && fieldDef.options && !fieldDef.options.includes(value)) {
      const updatedFields = formFields.map(f => f.key === field ? { ...f, options: [...(f.options || []), value] } : f);
      setFormFields(updatedFields);
      storage.set('formFieldsConfig', updatedFields);
      persistEditorFieldLibrary({ formFields: updatedFields });
    }
  };

  const removeTag = (field: string, value: string) => {
    const current = (reportData as any)[field] || [];
    const next = { ...reportData, [field]: current.filter((v: string) => v !== value) };
    setReportData(next);
    stateRef.current = { ...stateRef.current, reportData: next };
    saveDraftToStorage();
  };

  const removeMultiOption = (field: string, value: string) => {
    const current = multiSelectOptions[field] || [];
    const next = { ...multiSelectOptions, [field]: current.filter(v => v !== value) };
    setMultiSelectOptions(next);
    storage.set('multiSelectOptions', next);
    persistEditorFieldLibrary({ multiSelectOptions: next });
    // Sync to formFieldsConfig
    const fieldDef = formFields.find(f => f.key === field);
    if (fieldDef && fieldDef.options) {
      const updatedFields = formFields.map(f => f.key === field ? { ...f, options: (f.options || []).filter(v => v !== value) } : f);
      setFormFields(updatedFields);
      storage.set('formFieldsConfig', updatedFields);
      persistEditorFieldLibrary({ formFields: updatedFields });
    }
  };

  const removeAnesthesiaOption = (value: string) => {
    const next = anesthesiaOptions.filter(v => v !== value);
    setAnesthesiaOptions(next);
    storage.set('anesthesiaOptions', next);
    persistEditorFieldLibrary({ anesthesiaOptions: next });
  };

  // Close dropdowns on outside click
  useEffect(() => {
    const handleDocClick = (e: MouseEvent) => {
      const target = e.target as HTMLElement;
      if (!target.closest('.select-dropdown-root')) {
        setOpenDropdown(null);
      }
    };
    document.addEventListener('click', handleDocClick);
    return () => document.removeEventListener('click', handleDocClick);
  }, []);

  const saveReport = async (status: 'draft' | 'completed') => {
    if (status === 'completed' && (!reportData.patientName || !reportData.hospitalId)) {
      alert('请填写患者姓名和住院号');
      return;
    }

    if (status === 'completed') {
      const hasSignatureField = editorRef.current?.querySelector('[data-bind="surgeonSignature"]');
      if (hasSignatureField) {
        const hasSignatureImage = !!currentUser?.signature;
        if (!hasSignatureImage) {
          const proceed = window.confirm('提示：模板中包含【手术者签名】字段，但您的账号尚未上传电子签名图片。报告中将不显示签名图片，是否继续完成？');
          if (!proceed) return;
        }
      }
    }

    await waitForMediaUploads();
    const content = editorRef.current?.innerHTML || '';
    const now = new Date().toISOString();
    const reports = storage.get<Report[]>('reports', []);
    const old = reportId ? reports.find(r => r.id === reportId) : undefined;
    const nextRevision = old && old.status === 'completed' ? (old.revision || 1) + 1 : (old?.revision || 1);
    const finalReport: Report = {
      ...(reportData as Report),
      id: reportId || 'RPT_' + Date.now(),
      content,
      author: currentUser?.username || '',
      authorName: currentUser?.name || '',
      createdAt: reportData.createdAt || now.split('T')[0],
      department: reportData.department || currentUser?.department || '',
      status,
      revision: nextRevision,
      capturedFrames: stateRef.current.capturedFrames,
      videos: stateRef.current.videos,
      updatedAt: now
    };

    let savedReport = finalReport;
    let apiSaved = false;
    try {
      savedReport = await saveReportToApi(finalReport, reportId || undefined);
      apiSaved = true;
    } catch (error) {
      if (!isLocalFallbackEnabled()) {
        const message = getApiErrorMessage(error, '后端服务不可用');
        alert(`保存失败：${message}`);
        if (error instanceof ApiError && error.status === 401) navigate('/');
        return;
      }
    }

    let updatedReports: Report[];
    if (reportId) {
      const history = old?.history ? [...old.history] : [];
      if (old) {
        history.push({ 
          content: old.content, 
          updatedAt: old.updatedAt || old.createdAt, 
          updatedBy: currentUser?.name || currentUser?.username || '',
          action: status === 'completed' ? 'complete_report' : 'save_draft',
          revision: old.revision || 1
        });
      }
      const nextHistory = savedReport.history && savedReport.history.length > 0 ? savedReport.history : history;
      updatedReports = reports.map(r => r.id === reportId ? { ...savedReport, history: nextHistory } : r);
    } else {
      updatedReports = [...reports, savedReport];
      if (draftKey) storage.remove(draftKey);
    }

    if (apiSaved || isLocalFallbackEnabled()) {
      storage.set('reports', updatedReports);
    }
    if (draftKey) storage.remove(draftKey);
    setIsSaved(true);
    setTimeout(() => setIsSaved(false), 3000);
    if (status === 'completed') navigate('/report-manage');
  };

  const saveAsPersonalTemplate = async () => {
    if (!currentUser || !editorRef.current) return;
    const name = window.prompt('请输入个人模板名称：', `${reportData.title || '手术报告'}-我的模板`);
    if (!name || !name.trim()) return;
    const allTemplates = storage.get<Template[]>('templates', []);
    const newTemplate: Template = {
      id: `personal_${currentUser.username}_${Date.now()}`,
      name: name.trim(),
      desc: '我的个人模板',
      content: editorRef.current.innerHTML,
      createdAt: new Date().toISOString(),
      author: currentUser.username,
      fields: formFields,
      scope: 'personal',
      ownerUser: currentUser.username,
      department: currentUser.department || ''
    };
    let savedTemplate = newTemplate;
    let apiSaved = false;
    try {
      savedTemplate = await createTemplate(newTemplate);
      apiSaved = true;
    } catch (error) {
      if (!isLocalFallbackEnabled()) {
        const message = getApiErrorMessage(error, '后端服务不可用');
        alert(`保存个人模板失败：${message}`);
        if (error instanceof ApiError && error.status === 401) navigate('/');
        return;
      }
    }
    const updated = [...allTemplates.filter(t => t.id !== savedTemplate.id), savedTemplate];
    if (apiSaved || isLocalFallbackEnabled()) {
      storage.set('templates', updated);
    }
    const usable = getUsableTemplates(currentUser, updated);
    setTemplates(usable);
    setLoadedTemplateId(savedTemplate.id);
    stateRef.current = { ...stateRef.current, loadedTemplateId: savedTemplate.id };
    alert('个人模板已保存');
  };

  const handleEditorInput = (e: React.FormEvent<HTMLDivElement>) => {
    if (editorRef.current) {
      contentRef.current = editorRef.current.innerHTML;
    }
    updatePageHeight();
    saveDraftToStorage();

    const target = e.target as HTMLElement;
    if (target && target.hasAttribute('data-bind')) {
      const fieldKey = target.getAttribute('data-bind')!;
      const newValue = target.innerText;

      const fieldDef = formFields.find(f => f.key === fieldKey);
      if (fieldKey === 'startTime') {
        let raw = newValue;
        if (fieldDef?.timeFormat && (fieldDef.timeFormat.includes('hh') || fieldDef.timeFormat.includes('A'))) {
          raw = parseTimeFromFormat(newValue, fieldDef.timeFormat);
        }
        const parts = raw.split(':');
        setReportData((prev) => {
          const next = { ...prev, startHour: parts[0] || '', startMinute: parts[1] || '' };
          stateRef.current = { ...stateRef.current, reportData: next };
          return next;
        });
      } else if (fieldKey === 'endTime') {
        let raw = newValue;
        if (fieldDef?.timeFormat && (fieldDef.timeFormat.includes('hh') || fieldDef.timeFormat.includes('A'))) {
          raw = parseTimeFromFormat(newValue, fieldDef.timeFormat);
        }
        const parts = raw.split(':');
        setReportData((prev) => {
          const next = { ...prev, endHour: parts[0] || '', endMinute: parts[1] || '' };
          stateRef.current = { ...stateRef.current, reportData: next };
          return next;
        });
      } else {
        let raw = newValue;
        if (fieldDef?.type === 'date') {
          raw = parseDateFromFormat(newValue, fieldDef.timeFormat);
        } else if (fieldDef?.type === 'time') {
          raw = parseTimeFromFormat(newValue, fieldDef.timeFormat);
        }
        setReportData((prev) => {
          const next = { ...prev, [fieldKey]: raw };
          stateRef.current = { ...stateRef.current, reportData: next };
          return next;
        });
      }
    }
  };

  // Sync form state -> rich text field values
  useEffect(() => {
    if (!editorRef.current) return;
    const bindNodes = editorRef.current.querySelectorAll('[data-bind]');
    bindNodes.forEach((node) => {
      const el = node as HTMLElement;
      const fieldKey = el.getAttribute('data-bind')!;

      if (fieldKey === 'surgeonSignature') {
        const signatureData = currentUser?.signature;
        if (signatureData) {
          const imgHtml = `<img src="${signatureData}" class="report-signature-img" alt="签名" draggable="false" />`;
          if (el.innerHTML !== imgHtml) {
            el.innerHTML = imgHtml;
            el.style.border = 'none';
            el.style.backgroundColor = 'transparent';
          }
        } else {
          const placeholder = '【请上传电子签】';
          if (el.innerText !== placeholder) {
            el.innerText = placeholder;
            el.style.border = '';
            el.style.backgroundColor = '';
          }
        }
        return;
      }

      let newValue = '';
      const fieldDef = formFields.find(f => f.key === fieldKey);
      if (fieldKey === 'startTime') {
        newValue = `${reportData.startHour || ''}:${reportData.startMinute || ''}`;
        if (newValue === ':') newValue = '';
        newValue = formatTimeDisplay(newValue, fieldDef?.timeFormat);
      } else if (fieldKey === 'endTime') {
        newValue = `${reportData.endHour || ''}:${reportData.endMinute || ''}`;
        if (newValue === ':') newValue = '';
        newValue = formatTimeDisplay(newValue, fieldDef?.timeFormat);
      } else {
        const rawValue = (reportData as any)[fieldKey];
        if (Array.isArray(rawValue)) {
          newValue = rawValue.join(', ');
        } else if (rawValue !== undefined && rawValue !== null) {
          newValue = String(rawValue);
        }
        if (fieldDef?.type === 'date') {
          newValue = formatDateDisplay(newValue, fieldDef.timeFormat);
        } else if (fieldDef?.type === 'time') {
          newValue = formatTimeDisplay(newValue, fieldDef.timeFormat);
        }
      }

      if (el.innerText !== newValue) {
        el.innerText = newValue;
      }
    });
  }, [reportData]);

  if (!currentUser) return null;

  const hasVisibleTemplates = templates.length > 0;

  return (
    <div className="flex h-screen bg-bg overflow-hidden">
      <Sidebar />
      
      <div className="flex-1 flex flex-col overflow-hidden">
        {/* Header */}
        <header className="h-20 bg-white border-b border-border flex items-center justify-between px-8 shrink-0">
          <div className="flex items-center gap-4">
            <button 
              onClick={() => navigate('/report-manage')}
              className="p-2 rounded-lg hover:bg-slate-100 text-text-muted transition-colors"
            >
              <ChevronLeft size={24} />
            </button>
            <div>
              <h1 className="text-lg font-bold text-text-main">图文报告生成</h1>
              <p className="text-[10px] text-text-muted mt-0.5 uppercase tracking-wider font-bold">
                {reportId ? `编辑报告: ${reportId}` : '新建手术报告'}
              </p>
            </div>
          </div>
          <div className="flex items-center gap-4">
            <div className="flex items-center gap-1">
              <span className="text-xs text-text-muted whitespace-nowrap">当前模板（及重置模板）：</span>
              <select 
                value=""
                onChange={(e) => {
                  const tplId = e.target.value;
                  const tpl = templates.find(t => t.id === tplId);
                  if (tpl && window.confirm(`确定要应用模板 "${tpl.name}" 吗？当前内容、基本信息、视频分析将被重置。`)) {
                    setPendingTemplateId(tplId);
                  }
                }}
                className="h-8 px-2 border border-border rounded-lg text-xs bg-white cursor-pointer focus:outline-hidden focus:border-accent"
              >
                <option value="" disabled>
                  {templates.find(t => t.id === loadedTemplateId)?.name || '无'}
                </option>
                {templates.map(tpl => (
                  <option key={tpl.id} value={tpl.id}>{tpl.name}</option>
                ))}
              </select>
            </div>
            {isSaved && (
              <span className="text-xs text-green-600 font-bold flex items-center gap-1">
                <Check size={14} />
                已保存
              </span>
            )}
            <button 
              onClick={() => saveReport('draft')}
              className="px-6 py-2.5 bg-slate-100 text-text-muted rounded-lg text-sm font-semibold hover:bg-slate-200 transition-colors"
            >
              保存草稿
            </button>
            {currentUser.role === 'user' && (
              <button
                onClick={saveAsPersonalTemplate}
                className="px-4 py-2.5 bg-slate-100 text-text-muted rounded-lg text-sm font-semibold hover:bg-slate-200 transition-colors"
              >
                保存为我的模板
              </button>
            )}
            <button 
              onClick={() => saveReport('completed')}
              className="btn-accent inline-flex items-center gap-2"
            >
              <Check size={16} />
              完成报告
            </button>
            <button 
              onClick={() => setExportModalOpen(true)}
              className="p-2.5 rounded-lg bg-slate-100 text-text-muted hover:bg-slate-200 transition-colors"
              title="下载"
            >
              <Download size={18} />
            </button>
            <button 
              onClick={() => editorRef.current && printDocument(editorRef.current.innerHTML)}
              className="p-2.5 rounded-lg bg-slate-100 text-text-muted hover:bg-slate-200 transition-colors"
            >
              <Printer size={18} />
            </button>
          </div>
        </header>

        <div className="flex-1 flex overflow-hidden">
          {/* Editor Main */}
          <div className="flex-1 flex flex-col overflow-hidden border-r border-border">
            {/* Toolbar */}
            <div className="flex items-center gap-1 p-3 border-b border-border bg-slate-50 shrink-0 overflow-x-auto no-scrollbar">
              <div className="flex gap-1 pr-3 mr-3 border-r border-border">
                <button onClick={() => execCmd('undo')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="撤销"><Undo size={16} /></button>
                <button onClick={() => execCmd('redo')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="重做"><Redo size={16} /></button>
              </div>
              <div className="flex gap-1 pr-3 mr-3 border-r border-border">
                <select 
                  onMouseDown={(e) => e.preventDefault()}
                  onChange={(e) => { execCmd('fontName', e.target.value); e.target.value = ''; }}
                  className="h-9 px-3 border border-border rounded-lg text-xs bg-white cursor-pointer focus:outline-hidden focus:border-accent"
                >
                  <option value="">字体</option>
                  <option value="SimSun">宋体</option>
                  <option value="Microsoft YaHei">微软雅黑</option>
                  <option value="SimHei">黑体</option>
                  <option value="KaiTi">楷体</option>
                </select>
                <select
                  onMouseDown={(e) => e.preventDefault()}
                  onChange={(e) => { if (e.target.value) { execCmd('fontSize', e.target.value); } e.target.value = ''; }}
                  className="h-9 px-3 border border-border rounded-lg text-xs bg-white cursor-pointer focus:outline-hidden focus:border-accent"
                >
                  <option value="">字号</option>
                  <option value="3">12pt</option>
                  <option value="4">14pt</option>
                  <option value="5">18pt</option>
                  <option value="6">24pt</option>
                </select>
                <select
                  onMouseDown={(e) => e.preventDefault()}
                  onChange={(e) => { if (e.target.value) { changeLineHeight(e.target.value); } e.target.value = ''; }}
                  className="h-9 px-3 border border-border rounded-lg text-xs bg-white cursor-pointer focus:outline-hidden focus:border-accent"
                >
                  <option value="">行距</option>
                  <option value="1">1.0</option>
                  <option value="1.5">1.5</option>
                  <option value="2">2.0</option>
                </select>
              </div>
              <div className="flex gap-1 pr-3 mr-3 border-r border-border">
                <button onClick={() => execCmd('bold')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="粗体"><Bold size={16} /></button>
                <button onClick={() => execCmd('italic')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="斜体"><Italic size={16} /></button>
                <button onClick={() => execCmd('underline')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="下划线"><Underline size={16} /></button>
                <div className="relative flex items-center">
                  <input 
                    type="color" 
                    onChange={(e) => execCmd('foreColor', e.target.value)}
                    className="w-9 h-9 p-1.5 bg-transparent border-none cursor-pointer rounded-lg hover:bg-white transition-colors"
                    title="文字颜色"
                  />
                </div>
              </div>
              <div className="flex gap-1 pr-3 mr-3 border-r border-border">
                <button onMouseDown={(e) => e.preventDefault()} onClick={() => changeAlignment('left')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="左对齐"><AlignLeft size={16} /></button>
                <button onMouseDown={(e) => e.preventDefault()} onClick={() => changeAlignment('center')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="居中"><AlignCenter size={16} /></button>
                <button onMouseDown={(e) => e.preventDefault()} onClick={() => changeAlignment('right')} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="右对齐"><AlignRight size={16} /></button>
              </div>
              <div className="flex gap-1">
                <button onClick={insertTable} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="表格"><Table size={16} /></button>
                <button onClick={insertImage} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-white text-text-muted hover:text-text-main transition-colors" title="插入图片占位符"><ImageIcon size={16} /></button>
                <button onMouseDown={(e) => e.preventDefault()} onClick={insertAiRegion} className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-blue-50 text-blue-500 transition-colors" title="插入AI可编辑区域"><Bot size={16} /></button>
              </div>
            </div>

            {/* Editor Content */}
            <div 
              className="editor-content-wrapper print-wrapper"
              onDragOver={(e) => e.preventDefault()}
              onDrop={(e) => {
                const target = e.target as HTMLElement;
                const placeholder = target.closest('.image-placeholder') as HTMLElement;
                if (placeholder) handleDrop(e, placeholder);
              }}
            >
              <div 
                ref={editorRef}
                contentEditable
                onInput={handleEditorInput}
                onBlur={() => { contentRef.current = editorRef.current?.innerHTML || ''; saveDraftToStorage(); }}
                className="editor-content print-content"
              >
              </div>
            </div>
          </div>

          {/* Right Sidebar */}
          <aside className="w-[380px] bg-sidebar-bg flex flex-col shrink-0 overflow-hidden">
            <div className="flex border-b border-border">
              {(['info', 'video', 'ai'] as const).map(tab => (
                <button 
                  key={tab}
                  onClick={() => { setActiveTab(tab); stateRef.current = { ...stateRef.current, activeTab: tab }; saveDraftToStorage(); }}
                  className={`flex-1 py-4 text-xs font-bold transition-all border-b-2 uppercase tracking-wider flex items-center justify-center gap-1.5 ${
                    activeTab === tab ? 'text-accent border-accent' : 'text-text-muted border-transparent hover:text-text-main'
                  }`}
                >
                  {tab === 'ai' && <Bot size={16} className={activeTab === 'ai' ? 'text-accent' : 'text-text-muted'} />}
                  {tab === 'info' ? '基本信息' : tab === 'video' ? '视频分析' : 'AI撰写'}
                </button>
              ))}
            </div>

            <div className="flex-1 overflow-y-auto p-6 space-y-8">
              {activeTab === 'info' && (
                <div className="report-info-form space-y-4">
                  {(() => {
                    const topKeys = ['patientName', 'hospitalId', 'title'];
                    const contentHtml = contentRef.current || editorRef.current?.innerHTML || '';
                    return [...formFields.filter(f => f.visibleInForm)].sort((a, b) => {
                      const aTop = topKeys.indexOf(a.key);
                      const bTop = topKeys.indexOf(b.key);
                      if (aTop !== -1 && bTop !== -1) return aTop - bTop;
                      if (aTop !== -1) return -1;
                      if (bTop !== -1) return 1;
                      const aIndex = contentHtml.indexOf(`data-bind="${a.key}"`);
                      const bIndex = contentHtml.indexOf(`data-bind="${b.key}"`);
                      if (aIndex === -1 && bIndex === -1) return 0;
                      if (aIndex === -1) return 1;
                      if (bIndex === -1) return -1;
                      return aIndex - bIndex;
                    });
                  })().map(field => {
                    const isRequired = field.isSystemLocked;
                    const hasError = isRequired && touched[field.key] && !(reportData as any)[field.key];

                    if (field.type === 'text' || field.type === 'date') {
                      const inputType = field.type === 'date' ? 'date' : 'text';
                      return (
                        <div key={field.key} id={`input-${field.key}`} onClick={() => setActiveFieldKey(field.key)} className={`${field.category === '填空' && formFields.filter(f2 => f2.visibleInForm && f2.type === 'text' && f2.isSystemLocked).length > 1 && (field.key === 'patientName' || field.key === 'hospitalId') ? 'flex-1 space-y-1' : 'space-y-1'} p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
                          <label className="block text-xs font-bold text-text-main">
                            {field.label} {isRequired && <span className="text-red-500">*</span>}
                          </label>
                          <input
                            type={inputType}
                            value={(reportData as any)[field.key] || ''}
                            onChange={(e) => { const next = { ...reportData, [field.key]: e.target.value }; setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage(); }}
                            onBlur={() => setTouched(t => ({ ...t, [field.key]: true }))}
                            className={`input-minimal ${hasError ? 'border-red-500' : ''}`}
                            placeholder={field.label}
                          />
                        </div>
                      );
                    }

                    if (field.type === 'single_select') {
                      const isOpen = openDropdown === field.key;
                      const opts = field.options || (field.key === 'anesthesiaType' ? anesthesiaOptions : []);
                      return (
                        <div key={field.key} id={`input-${field.key}`} onClick={() => setActiveFieldKey(field.key)} className={`space-y-1 select-dropdown-root relative p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
                          <label className="block text-xs font-bold text-text-main">{field.label}</label>
                          <div
                            className="w-full px-3 py-2 border border-border rounded-lg bg-white flex items-center min-h-[42px] cursor-text"
                            onClick={() => setOpenDropdown(field.key)}
                          >
                            <input
                              type="text"
                              className="outline-none text-sm flex-1 bg-transparent"
                              placeholder={`输入或选择${field.label}`}
                              value={(reportData as any)[field.key] || ''}
                              onChange={(e) => { const next = { ...reportData, [field.key]: e.target.value }; setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage(); }}
                              onFocus={() => setOpenDropdown(field.key)}
                              onKeyDown={(e) => {
                                if (e.key === 'Enter') {
                                  e.preventDefault();
                                  const val = (e.target as HTMLInputElement).value.trim();
                                  if (val) {
                                    const next = { ...reportData, [field.key]: val };
                                    setReportData(next);
                                    stateRef.current = { ...stateRef.current, reportData: next };
                                    saveDraftToStorage();
                                    if (!opts.includes(val)) {
                                      const updatedOpts = [...opts, val];
                                      if (field.key === 'anesthesiaType') {
                                        setAnesthesiaOptions(updatedOpts);
                                        storage.set('anesthesiaOptions', updatedOpts);
                                      }
                                      const updatedFields = formFields.map(f => f.key === field.key ? { ...f, options: updatedOpts } : f);
                                      setFormFields(updatedFields);
                                      storage.set('formFieldsConfig', updatedFields);
                                    }
                                    setOpenDropdown(null);
                                  }
                                }
                              }}
                            />
                          </div>
                          {isOpen && (
                            <div className="absolute z-10 left-0 right-0 top-full mt-1 bg-white border border-border rounded-lg shadow-lg max-h-[150px] overflow-y-auto">
                              {opts.map(opt => (
                                <div
                                  key={opt}
                                  className="px-3 py-2 text-sm hover:bg-slate-50 cursor-pointer flex justify-between items-center"
                                  onClick={() => { const next = { ...reportData, [field.key]: opt }; setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage(); setOpenDropdown(null); }}
                                >
                                  <span>{opt}</span>
                                  <span
                                    className="text-red-500 text-xs hover:bg-red-50 px-1 rounded"
                                    onClick={(e) => {
                                      e.stopPropagation();
                                      const updatedOpts = opts.filter(v => v !== opt);
                                      if (field.key === 'anesthesiaType') {
                                        setAnesthesiaOptions(updatedOpts);
                                        storage.set('anesthesiaOptions', updatedOpts);
                                      }
                                      const updatedFields = formFields.map(f => f.key === field.key ? { ...f, options: updatedOpts } : f);
                                      setFormFields(updatedFields);
                                      storage.set('formFieldsConfig', updatedFields);
                                    }}
                                  >×</span>
                                </div>
                              ))}
                              {opts.length === 0 && (
                                <div className="px-3 py-2 text-xs text-text-muted">暂无选项</div>
                              )}
                            </div>
                          )}
                        </div>
                      );
                    }

                    if (field.type === 'multi_select') {
                      const isOpen = openDropdown === field.key;
                      const opts = field.options || multiSelectOptions[field.key] || [];
                      const rawValue = (reportData as any)[field.key];
                      const currentValues = Array.isArray(rawValue) ? rawValue : (rawValue ? [String(rawValue)] : []);
                      const displayText = currentValues.join(', ');

                      const parseMultiInput = (text: string): string[] => {
                        return Array.from(new Set(text.split(/[,，;；、]/).map(s => s.trim()).filter(Boolean)));
                      };

                      const handleMultiCommit = (text: string) => {
                        const values = parseMultiInput(text);
                        const next = { ...reportData, [field.key]: values };
                        setReportData(next);
                        stateRef.current = { ...stateRef.current, reportData: next };
                        saveDraftToStorage();

                        const currentOpts = field.options || multiSelectOptions[field.key] || [];
                        const newOpts = values.filter(v => !currentOpts.includes(v));
                        if (newOpts.length > 0) {
                          const mergedOpts = [...currentOpts, ...newOpts];
                          const nextMulti = { ...multiSelectOptions, [field.key]: mergedOpts };
                          setMultiSelectOptions(nextMulti);
                          storage.set('multiSelectOptions', nextMulti);
                          const fieldDef = formFields.find(f => f.key === field.key);
                          if (fieldDef) {
                            const updatedFields = formFields.map(f => f.key === field.key ? { ...f, options: mergedOpts } : f);
                            setFormFields(updatedFields);
                            storage.set('formFieldsConfig', updatedFields);
                          }
                        }
                      };

                      const currentInputText = multiInputText[field.key] !== undefined ? multiInputText[field.key] : displayText;

                      return (
                        <div key={field.key} id={`input-${field.key}`} onClick={() => setActiveFieldKey(field.key)} className={`space-y-1 select-dropdown-root relative p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
                          <label className="block text-xs font-bold text-text-main">{field.label}（可多选）</label>
                          <div
                            className="w-full px-3 py-2 border border-border rounded-lg bg-white flex flex-wrap gap-1 items-center min-h-[42px] cursor-text"
                            onClick={() => setOpenDropdown(field.key)}
                          >
                            <input
                              type="text"
                              className="outline-none text-sm w-full bg-transparent"
                              placeholder="输入或选择，多个用逗号分隔"
                              value={currentInputText}
                              onChange={(e) => {
                                setMultiInputText(prev => ({ ...prev, [field.key]: e.target.value }));
                              }}
                              onFocus={() => setOpenDropdown(field.key)}
                              onBlur={(e) => {
                                handleMultiCommit(e.target.value);
                                setMultiInputText(prev => {
                                  const next = { ...prev };
                                  delete next[field.key];
                                  return next;
                                });
                              }}
                              onKeyDown={(e) => {
                                if (e.key === 'Enter') {
                                  e.preventDefault();
                                  handleMultiCommit((e.target as HTMLInputElement).value);
                                  setMultiInputText(prev => {
                                    const next = { ...prev };
                                    delete next[field.key];
                                    return next;
                                  });
                                }
                              }}
                            />
                          </div>
                          {isOpen && (
                            <div className="absolute z-10 left-0 right-0 top-full mt-1 bg-white border border-border rounded-lg shadow-lg max-h-[150px] overflow-y-auto">
                              {opts.map(opt => (
                                <div
                                  key={opt}
                                  className="px-3 py-2 text-sm hover:bg-slate-50 cursor-pointer flex justify-between items-center"
                                  onClick={() => {
                                    const base = multiInputText[field.key] !== undefined ? multiInputText[field.key] : displayText;
                                    const newText = base.length > 0 ? `${base}, ${opt}` : opt;
                                    setMultiInputText(prev => ({ ...prev, [field.key]: newText }));
                                    handleMultiCommit(newText);
                                    setMultiInputText(prev => {
                                      const next = { ...prev };
                                      delete next[field.key];
                                      return next;
                                    });
                                  }}
                                >
                                  <span>{opt}</span>
                                  <span
                                    className="text-red-500 text-xs hover:bg-red-50 px-1 rounded"
                                    onClick={(e) => { e.stopPropagation(); removeMultiOption(field.key, opt); }}
                                  >×</span>
                                </div>
                              ))}
                              {opts.length === 0 && (
                                <div className="px-3 py-2 text-xs text-text-muted">暂无选项</div>
                              )}
                            </div>
                          )}
                        </div>
                      );
                    }

                    if (field.type === 'time') {
                      const is12h = field.timeFormat ? (field.timeFormat.includes('hh') || field.timeFormat.includes('A')) : false;

                      if (field.key === 'startTime' || field.key === 'endTime') {
                        const hourKey = field.key === 'startTime' ? 'startHour' : 'endHour';
                        const minuteKey = field.key === 'startTime' ? 'startMinute' : 'endMinute';
                        const h24val = parseInt((reportData as any)[hourKey]) || 0;
                        const m = (reportData as any)[minuteKey] || '';
                        const { h: h12, isPM } = from24h(h24val);

                        return (
                          <div key={field.key} id={`input-${field.key}`} onClick={() => setActiveFieldKey(field.key)} className={`space-y-1 p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
                            <label className="block text-xs font-bold text-text-main">{field.label}</label>
                            <div className="flex items-center gap-2">
                              <select
                                value={is12h ? String(h12).padStart(2, '0') : ((reportData as any)[hourKey] || '')}
                                onChange={(e) => {
                                  let h24new = parseInt(e.target.value) || 0;
                                  if (is12h) {
                                    const currentPM = from24h(parseInt((reportData as any)[hourKey]) || 0).isPM;
                                    h24new = to24h(h24new, currentPM);
                                  }
                                  const next = { ...reportData, [hourKey]: String(h24new).padStart(2, '0') };
                                  setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage();
                                }}
                                className="input-minimal bg-white flex-1"
                              >
                                <option value="">--</option>
                                {(is12h ? hour12Options : hourOptions).map(h => <option key={h} value={h}>{h}</option>)}
                              </select>
                              <span className="text-text-muted">:</span>
                              <select
                                value={m}
                                onChange={(e) => { const next = { ...reportData, [minuteKey]: e.target.value }; setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage(); }}
                                className="input-minimal bg-white flex-1"
                              >
                                <option value="">--</option>
                                {minuteOptions.map(mo => <option key={mo} value={mo}>{mo}</option>)}
                              </select>
                              {is12h && (
                                <select
                                  value={isPM ? '下午' : '上午'}
                                  onChange={(e) => {
                                    const isPMnew = e.target.value === '下午';
                                    const currentH12 = from24h(parseInt((reportData as any)[hourKey]) || 0).h;
                                    const h24new = to24h(currentH12, isPMnew);
                                    const next = { ...reportData, [hourKey]: String(h24new).padStart(2, '0') };
                                    setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage();
                                  }}
                                  className="input-minimal bg-white flex-1"
                                >
                                  <option value="上午">上午</option>
                                  <option value="下午">下午</option>
                                </select>
                              )}
                            </div>
                          </div>
                        );
                      }

                      // 通用 time 字段
                      const timeVal = (reportData as any)[field.key] || '';
                      const [h24str, mstr] = timeVal.split(':');
                      const h24 = parseInt(h24str) || 0;
                      const m = mstr || '';
                      const { h: h12g, isPM: isPMg } = from24h(h24);

                      return (
                        <div key={field.key} id={`input-${field.key}`} onClick={() => setActiveFieldKey(field.key)} className={`space-y-1 p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
                          <label className="block text-xs font-bold text-text-main">{field.label}</label>
                          <div className="flex items-center gap-2">
                            <select
                              value={is12h ? String(h12g).padStart(2, '0') : (h24str || '')}
                              onChange={(e) => {
                                let h24new = parseInt(e.target.value) || 0;
                                if (is12h) h24new = to24h(h24new, isPMg);
                                const nextVal = `${String(h24new).padStart(2, '0')}:${m || '00'}`;
                                const next = { ...reportData, [field.key]: nextVal };
                                setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage();
                              }}
                              className="input-minimal bg-white flex-1"
                            >
                              <option value="">--</option>
                              {(is12h ? hour12Options : hourOptions).map(h => <option key={h} value={h}>{h}</option>)}
                            </select>
                            <span className="text-text-muted">:</span>
                            <select
                              value={m}
                              onChange={(e) => {
                                const nextVal = `${String(h24).padStart(2, '0')}:${e.target.value}`;
                                const next = { ...reportData, [field.key]: nextVal };
                                setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage();
                              }}
                              className="input-minimal bg-white flex-1"
                            >
                              <option value="">--</option>
                              {minuteOptions.map(mo => <option key={mo} value={mo}>{mo}</option>)}
                            </select>
                            {is12h && (
                              <select
                                value={isPMg ? '下午' : '上午'}
                                onChange={(e) => {
                                  const isPMnew = e.target.value === '下午';
                                  const h24new = to24h(h12g, isPMnew);
                                  const nextVal = `${String(h24new).padStart(2, '0')}:${m || '00'}`;
                                  const next = { ...reportData, [field.key]: nextVal };
                                  setReportData(next); stateRef.current = { ...stateRef.current, reportData: next }; saveDraftToStorage();
                                }}
                                className="input-minimal bg-white flex-1"
                              >
                                <option value="上午">上午</option>
                                <option value="下午">下午</option>
                              </select>
                            )}
                          </div>
                        </div>
                      );
                    }

                    return null;
                  })}
                </div>
              )}

              {activeTab === 'video' && (
                <div className="space-y-2">
                  <input 
                    ref={videoInputRef}
                    type="file" 
                    accept="video/*" 
                    multiple 
                    className="hidden" 
                    onChange={handleVideoUpload} 
                  />

                  <div className="flex gap-2 overflow-x-auto pb-2 no-scrollbar items-center">
                    {videos.map((v, i) => (
                      <div 
                        key={v.id}
                        className={`shrink-0 w-24 p-1.5 border-2 rounded-xl cursor-pointer transition-all relative group ${
                          currentVideoIndex === i ? 'border-accent bg-white shadow-sm' : 'border-transparent'
                        }`}
                      >
                        <div 
                          onClick={() => selectVideo(i)}
                          className="aspect-video bg-slate-900 rounded-lg flex items-center justify-center text-white"
                        >
                          <Play size={16} />
                        </div>
                        <div 
                          onClick={() => selectVideo(i)}
                          className="text-[9px] font-bold text-text-main truncate mt-1.5 px-1"
                        >{v.name}</div>
                        <button
                          onClick={() => removeVideo(v.id)}
                          className="absolute -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center text-[10px] opacity-0 group-hover:opacity-100 transition-all shadow-sm"
                        >
                          <X size={12} />
                        </button>
                      </div>
                    ))}
                    <button
                      onClick={() => videoInputRef.current?.click()}
                      className="shrink-0 w-24 h-[68px] flex flex-col items-center justify-center gap-1 border-2 border-dashed border-border rounded-xl hover:border-accent hover:bg-slate-50 transition-all text-text-muted hover:text-accent"
                    >
                      <Video size={18} />
                      <span className="text-[10px] font-bold">上传视频</span>
                    </button>
                  </div>

                  {currentVideoIndex !== -1 && videos.length > 0 && (
                    <div className="space-y-2">
                          <div className="relative bg-slate-900 rounded-2xl overflow-hidden aspect-video shadow-lg">
                            <video 
                              ref={videoRef}
                              src={videos[currentVideoIndex].url}
                              className="w-full h-full"
                              onTimeUpdate={() => setCurrentTime(videoRef.current?.currentTime || 0)}
                              onLoadedMetadata={() => {
                                const dur = videoRef.current?.duration || 0;
                                setDuration(dur);
                                setVideos(prev => prev.map((v, i) => i === currentVideoIndex ? { ...v, duration: dur } : v));
                              }}
                            />
                            {!isPlaying && (
                              <div className="absolute inset-0 flex items-center justify-center bg-black/40 cursor-pointer" onClick={togglePlay}>
                                <div className="w-16 h-16 rounded-full bg-white/20 backdrop-blur-md flex items-center justify-center text-white">
                                  <Play size={32} fill="currentColor" />
                                </div>
                              </div>
                            )}
                            <div className="absolute bottom-4 right-4 bg-black/60 backdrop-blur-md text-white text-[10px] font-bold px-3 py-1.5 rounded-full">
                              {formatTime(currentTime)} / {formatTime(duration)}
                            </div>
                          </div>
                          
                          <div className="flex items-center gap-4">
                            <button onClick={togglePlay} className="w-10 h-10 flex items-center justify-center bg-slate-100 rounded-xl text-text-main hover:bg-slate-200 transition-colors">
                              {isPlaying ? <Pause size={18} /> : <Play size={18} />}
                            </button>
                            <div className="flex-1 h-2 bg-slate-100 rounded-full overflow-hidden cursor-pointer relative" onClick={(e) => {
                              const rect = e.currentTarget.getBoundingClientRect();
                              const pos = (e.clientX - rect.left) / rect.width;
                              if (videoRef.current) videoRef.current.currentTime = pos * duration;
                            }}>
                              <div className="h-full bg-accent" style={{ width: `${(currentTime / duration) * 100}%` }} />
                            </div>
                          </div>

                          <div className="flex justify-between items-center pt-1 border-t border-border">
                            <span className="text-[10px] font-bold text-text-main uppercase tracking-wider">关键帧摘取</span>
                            <button 
                              onClick={captureFrame} 
                              className="px-4 py-2 bg-accent text-white rounded-lg text-[10px] font-bold uppercase tracking-wider hover:bg-blue-700 transition-colors shadow-sm"
                            >
                              截取当前帧
                            </button>
                          </div>

                          <div className="grid grid-cols-2 gap-3 max-h-[400px] overflow-y-auto pr-2 custom-scrollbar">
                            {capturedFrames.filter(f => f.videoIndex === currentVideoIndex).map((frame) => (
                              <div 
                                key={frame.id}
                                draggable
                                onDragStart={(e) => handleDragStart(e, frame)}
                                onClick={() => seekToFrame(frame)}
                                className={`group relative card-minimal p-1 border-2 cursor-grab active:cursor-grabbing hover:shadow-md transition-all ${
                                  frame.isManual ? 'border-yellow-400 hover:border-yellow-500' : 'border-transparent hover:border-accent'
                                }`}
                              >
                                <div className="relative">
                                  <img src={frame.dataUrl} className="w-full aspect-video object-cover rounded-lg" />
                                  {frame.isManual && <span className="manual-frame-badge">手动</span>}
                                </div>
                                <div className="text-[9px] font-bold text-text-muted mt-1.5 px-1 flex justify-between items-center">
                                  <span>{frame.timeFormatted}</span>
                                  <div className="flex items-center gap-2">
                                    <button
                                      onClick={(e) => { e.stopPropagation(); insertFrameToPlaceholder(frame); }}
                                      className="px-2 py-0.5 bg-accent text-white text-[9px] font-bold rounded-full shadow-sm opacity-0 group-hover:opacity-100 transition-opacity hover:bg-blue-700"
                                    >
                                      插入
                                    </button>
                                    <span className="text-accent opacity-0 group-hover:opacity-100 transition-opacity">可拖拽</span>
                                  </div>
                                </div>
                                <button 
                                  onClick={(e) => { e.stopPropagation(); setCapturedFrames(prev => prev.filter(f => f.id !== frame.id).sort((a, b) => a.time - b.time)); saveDraftToStorage(); }}
                                  className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-all shadow-md"
                                >
                                  <X size={14} />
                                </button>
                              </div>
                            ))}
                          </div>
                          <p className="text-[10px] text-text-muted text-center">
                            提示：点击关键帧可跳转视频位置，按键盘 ← → 键可微调进度
                          </p>
                        </div>
                      )}
                </div>
              )}

              {activeTab === 'ai' && (
                <div className="flex flex-col h-full bg-[#f8fafc] overflow-hidden">
                  {/* 聊天气泡记录区 */}
                  <div className="flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar">
                    {chatMessages.length === 0 ? (
                      <div className="text-center flex flex-col items-center justify-center h-full text-slate-400 space-y-3">
                        <Bot size={48} className="text-slate-300 opacity-50" />
                        <p className="text-xs">我是 SurClaw 智能助理，请选择参考框架和图片后随时与我对话。</p>
                      </div>
                    ) : (
                      chatMessages.map(msg => (
                        <div key={msg.id} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
                          <div className={`rounded-2xl px-4 py-2.5 max-w-[85%] text-sm ${msg.role === 'user' ? 'bg-blue-600 text-white rounded-tr-none shadow-md' : 'bg-white border border-slate-200 text-slate-700 rounded-tl-none shadow-sm'}`}>
                            <div>{msg.content}</div>
                            {msg.images && msg.images.length > 0 && (
                              <div className="flex gap-1.5 mt-2 flex-wrap">
                                {msg.images.map((src, idx) => (
                                  <img key={idx} src={src} className="w-10 h-10 object-cover rounded border border-white/30" />
                                ))}
                              </div>
                            )}
                          </div>
                        </div>
                      ))
                    )}
                    {isGenerating && (
                      <div className="flex justify-start">
                        <div className="bg-white border border-slate-200 rounded-2xl rounded-tl-none px-4 py-3 shadow-sm flex gap-1.5 items-center">
                          <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" />
                          <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '0.15s' }} />
                          <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '0.3s' }} />
                        </div>
                      </div>
                    )}
                  </div>

                  {/* 控制台与输入区 */}
                  <div className="bg-white border-t border-slate-200 p-4 space-y-3 shrink-0 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10">
                    {/* 区域锚定与沙盒控制 */}
                    <div className="flex items-center justify-between bg-slate-50 p-2 rounded-lg border border-slate-200">
                      <div className="flex items-center gap-2 flex-1">
                        <select
                          value={aiTargetRegion}
                          onChange={(e) => setAiTargetRegion(e.target.value)}
                          disabled={!aiModifyEnabled}
                          className="flex-1 w-0 px-2 py-1 border-none text-xs bg-transparent focus:ring-0 font-bold text-slate-700 disabled:opacity-50"
                        >
                          {checkAiRegions().length > 0 ? (
                            checkAiRegions().map((r: any) => <option key={r.id} value={r.id}>🎯 {r.title}</option>)
                          ) : (
                            <option value="none">无可用 AI 区域</option>
                          )}
                        </select>
                      </div>
                      <label className="flex items-center gap-1.5 shrink-0 pl-2 border-l border-slate-300 cursor-pointer">
                        <input
                          type="checkbox"
                          checked={aiModifyEnabled}
                          onChange={(e) => {
                            e.stopPropagation();
                            setAiModifyEnabled(e.target.checked);
                          }}
                          className="w-3.5 h-3.5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 cursor-pointer"
                        />
                        <span className="text-[11px] text-slate-600 font-bold">
                          允许修改正文
                        </span>
                      </label>
                    </div>

                    {/* 视觉参考上下文 - 编辑器中已插入的图片 */}
                    {editorImages.length > 0 && (
                      <div className="flex gap-2 overflow-x-auto pb-1 custom-scrollbar">
                        {editorImages.map(img => {
                          const isSelected = aiSelectedEditorImages.includes(img.id);
                          return (
                            <div key={img.id} onClick={() => setAiSelectedEditorImages(prev => isSelected ? prev.filter(id => id !== img.id) : [...prev, img.id])}
                              className={`relative shrink-0 w-12 aspect-video rounded overflow-hidden border-2 cursor-pointer transition-all ${isSelected ? 'border-blue-600' : 'border-transparent opacity-50'}`}>
                              <img src={img.src} className="w-full h-full object-cover" />
                              {isSelected && <div className="absolute top-0.5 right-0.5 bg-blue-600 rounded-full p-0.5"><Check size={8} className="text-white" /></div>}
                            </div>
                          );
                        })}
                      </div>
                    )}

                    {/* 自定义快捷指令胶囊 */}
                    <div className="flex flex-wrap gap-1.5 max-h-16 overflow-y-auto">
                      {quickPrompts.map((p, i) => (
                        <div key={i} className="group relative">
                          <button onClick={() => setChatInput(p)} className="px-3 py-1 bg-[#f1f5f9] hover:bg-blue-50 hover:text-blue-600 text-slate-600 text-[11px] rounded-full transition-colors whitespace-nowrap">
                            {p}
                          </button>
                          {isEditingPrompts && (
                            <button onClick={() => setQuickPrompts(prev => prev.filter((_, idx) => idx !== i))} className="absolute -top-1 -right-1 bg-red-500 text-white rounded-full p-0.5 scale-75 shadow-sm">
                              <X size={10} />
                            </button>
                          )}
                        </div>
                      ))}
                      <button onClick={() => {
                        if (isEditingPrompts) {
                          const newP = prompt('新增快捷指令：');
                          if (newP) setQuickPrompts([...quickPrompts, newP]);
                        } else {
                          setIsEditingPrompts(true);
                        }
                      }} className="px-2 py-1 bg-slate-100 text-slate-400 text-[11px] rounded-full hover:bg-slate-200">
                        {isEditingPrompts ? '+ 添加' : '⚙️'}
                      </button>
                      {isEditingPrompts && <button onClick={() => setIsEditingPrompts(false)} className="px-2 py-1 bg-blue-100 text-blue-600 text-[11px] rounded-full">完成</button>}
                      <button onClick={() => {
                        const data = {
                          exportAt: new Date().toISOString(),
                          url: window.location.href,
                          messages: chatMessages,
                          lastExchange: lastExchangeLog,
                          metadata: {
                            user: currentUser?.username || 'anonymous',
                            activeProvider: (() => { const s = storage.get<SystemSettings>('systemSettings', {} as SystemSettings); return s.activeAiProvider || 'kimi'; })(),
                            targetRegion: aiTargetRegion,
                            modifyEnabled: aiModifyEnabled,
                            chatInput,
                            uploadedImagesCount: aiUploadedImages.length,
                            selectedFramesCount: aiSelectedEditorImages.length
                          }
                        };
                        const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = `ai-logs-${Date.now()}.json`;
                        a.click();
                        URL.revokeObjectURL(url);
                      }} className="px-2 py-1 bg-slate-100 text-slate-500 text-[11px] rounded-full hover:bg-slate-200 ml-auto" title="导出 AI 日志（调试用）">
                        导出 AI 日志
                      </button>
                    </div>

                    {/* 沉浸式输入框 */}
                    <div className="relative border border-slate-300 rounded-xl bg-white shadow-inner focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-transparent transition-all">
                      {aiUploadedImages.length > 0 && (
                        <div className="flex gap-2 p-2 border-b border-slate-100 bg-slate-50 rounded-t-xl overflow-x-auto">
                          {aiUploadedImages.map(img => (
                            <div key={img.id} className="relative w-10 h-10 rounded overflow-hidden shadow-sm shrink-0">
                              <img src={img.dataUrl} className="w-full h-full object-cover" />
                              <button onClick={() => setAiUploadedImages(prev => prev.filter(i => i.id !== img.id))} className="absolute top-0 right-0 bg-red-500/80 text-white rounded-bl-md">
                                <X size={10} />
                              </button>
                            </div>
                          ))}
                        </div>
                      )}
                      <textarea
                        value={chatInput}
                        onChange={(e) => setChatInput(e.target.value)}
                        onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (!isGenerating) handleAIGenerate(chatInput); } }}
                        placeholder={isListening ? '正在将语音转为文字...' : '输入需求（按 Enter 发送）...'}
                        className="w-full min-h-[80px] p-3 pr-12 text-sm bg-transparent outline-none resize-none custom-scrollbar"
                      />
                      <div className="absolute bottom-2 right-2 flex items-center gap-1.5">
                        <label className="p-1.5 text-slate-400 hover:text-blue-600 bg-slate-50 hover:bg-blue-50 rounded-lg cursor-pointer transition-colors" title="上传外部图像">
                          <input type="file" accept="image/*" multiple className="hidden" onChange={(e) => {
                            const files = e.target.files;
                            if (!files) return;
                            Array.from(files).forEach((file: File) => {
                              const reader = new FileReader();
                              reader.onload = (ev) => { if (ev.target?.result) setAiUploadedImages(prev => [...prev, { id: Date.now(), dataUrl: ev.target!.result as string }]); };
                              reader.readAsDataURL(file);
                            });
                          }} />
                          <ImagePlus size={16} />
                        </label>
                        <button onClick={toggleListening} className={`p-1.5 rounded-lg transition-colors ${isListening ? 'text-red-500 bg-red-50 animate-pulse' : 'text-slate-400 hover:text-blue-600 bg-slate-50 hover:bg-blue-50'}`}>
                          {isListening ? <Mic size={16} /> : <MicOff size={16} />}
                        </button>
                        <button onClick={() => { if (!isGenerating && chatInput.trim()) handleAIGenerate(chatInput); }} disabled={isGenerating || !chatInput.trim()} className="p-1.5 rounded-lg transition-colors text-slate-400 hover:text-blue-600 bg-slate-50 hover:bg-blue-50 disabled:opacity-40 disabled:cursor-not-allowed" title="发送">
                          <Send size={16} />
                        </button>
                      </div>
                    </div>
                  </div>
                </div>
              )}
            </div>
          </aside>
        </div>
      </div>
      <canvas ref={canvasRef} className="hidden" />

      {placeholderModal.isOpen && (
        <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
          <div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
            <h3 className="text-lg font-bold text-text-main mb-4">插入图片占位符</h3>
            <div className="space-y-3">
              <div className="flex gap-2">
                <div className="flex-1">
                  <label className="block text-xs mb-1">宽度(px)</label>
                  <input type="number" value={placeholderModal.width} onChange={e => setPlaceholderModal({...placeholderModal, width: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
                </div>
                <div className="flex-1">
                  <label className="block text-xs mb-1">高度(px)</label>
                  <input type="number" value={placeholderModal.height} onChange={e => setPlaceholderModal({...placeholderModal, height: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
                </div>
              </div>
              <div>
                <label className="block text-xs mb-1">占位符类型</label>
                <div className="flex gap-2">
                  <button onClick={() => setPlaceholderModal({...placeholderModal, mode: 'frame'})} className={`flex-1 py-1.5 text-xs rounded border ${placeholderModal.mode === 'frame' ? 'bg-accent text-white border-accent' : 'bg-white text-slate-600 border-border'}`}>手术影像占位<br/><span className="text-[10px] opacity-80">（支持自动/拖拽插入）</span></button>
                  <button onClick={() => setPlaceholderModal({...placeholderModal, mode: 'manual'})} className={`flex-1 py-1.5 text-xs rounded border ${placeholderModal.mode === 'manual' ? 'bg-accent text-white border-accent' : 'bg-white text-slate-600 border-border'}`}>静态图片占位<br/><span className="text-[10px] opacity-80">（仅支持点击插入）</span></button>
                </div>
              </div>
            </div>
            <div className="mt-5 flex justify-end gap-2">
              <button onClick={() => setPlaceholderModal({...placeholderModal, isOpen: false})} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
              <button onClick={() => {
                const sel = window.getSelection();
                let node: Node | null = sel?.anchorNode ?? null;
                let inTable = false;
                while (node) {
                  if ((node as Element).nodeName === 'TD' || (node as Element).nodeName === 'TH') {
                    inTable = true;
                    break;
                  }
                  node = node.parentNode;
                }
                const w = parseInt(placeholderModal.width) || 200;
                const h = parseInt(placeholderModal.height) || 200;
                const modeAttr = placeholderModal.mode === 'manual' ? ' data-mode="manual"' : '';
                const hintText = '插入/点击放置图片';
                const id = 'ph_' + Date.now();
                let html: string;
                if (inTable) {
                  const styleStr = 'position:relative;display:flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;cursor:pointer;width:100%;height:100%;max-width:200px;max-height:200px;min-height:60px;margin:0 auto;';
                  html = `<div id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);display:block;width:100%;text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${hintText}</span></div>`;
                } else {
                  let styleStr = 'display:inline-block;text-align:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;position:relative;';
                  styleStr += `width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;line-height:${h}px;`;
                  const showShortText = w > 0 && w < 80;
                  const text = showShortText ? '插图' : hintText;
                  html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);display:block;width:100%;text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span></span>&#8203;`;
                }
                execCmd('insertHTML', html);
                setPlaceholderModal({...placeholderModal, isOpen: false});
              }} className="px-4 py-2 bg-accent text-white rounded text-sm">确认插入</button>
            </div>
          </div>
        </div>
      )}

      {tableModal.isOpen && (
        <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
          <div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
            <h3 className="text-lg font-bold text-text-main mb-4">插入表格</h3>
            <div className="space-y-3">
              <div className="flex gap-2">
                <div className="flex-1">
                  <label className="block text-xs mb-1">行数</label>
                  <input type="number" min="1" value={tableModal.rows} onChange={e => setTableModal({...tableModal, rows: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
                </div>
                <div className="flex-1">
                  <label className="block text-xs mb-1">列数</label>
                  <input type="number" min="1" value={tableModal.cols} onChange={e => setTableModal({...tableModal, cols: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
                </div>
              </div>
            </div>
            <div className="mt-5 flex justify-end gap-2">
              <button onClick={() => setTableModal({...tableModal, isOpen: false})} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
              <button onClick={() => {
                const rows = parseInt(tableModal.rows);
                const cols = parseInt(tableModal.cols);
                if (isNaN(rows) || isNaN(cols) || rows < 1 || cols < 1) {
                  setTableModal({...tableModal, isOpen: false});
                  return;
                }
                let table = '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; table-layout: fixed;">';
                for (let i = 0; i < rows; i++) {
                  table += '<tr>';
                  for (let j = 0; j < cols; j++) {
                    table += '<td style="padding: 8px; border: 1px solid #e2e8f0; vertical-align: top;">单元格</td>';
                  }
                  table += '</tr>';
                }
                table += '</table><p></p>';
                execCmd('insertHTML', table);
                setTableModal({...tableModal, isOpen: false});
              }} className="px-4 py-2 bg-accent text-white rounded text-sm">确认插入</button>
            </div>
          </div>
        </div>
      )}

      {exportModalOpen && (
        <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
          <div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
            <h3 className="text-lg font-bold text-text-main mb-4">导出报告</h3>
            <div className="space-y-3">
              <button
                onClick={() => {
                  const ts = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().replace(/[:.]/g, '-').slice(0, 16);
                  const title = reportData.title || '无标题';
                  const patient = reportData.patientName || '未知';
                  const hid = reportData.hospitalId || '无号';
                  printDocument(editorRef.current?.innerHTML || '', `图文报告-${title}-${patient}-${hid}-${ts}`);
                  setExportModalOpen(false);
                }}
                className="w-full py-2.5 bg-accent text-white rounded text-sm font-semibold hover:opacity-90 transition-colors"
              >导出 PDF</button>
              <button
                onClick={() => {
                  const ts = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().replace(/[:.]/g, '-').slice(0, 16);
                  const title = reportData.title || '无标题';
                  const patient = reportData.patientName || '未知';
                  const hid = reportData.hospitalId || '无号';
                  const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' });
                  const url = URL.createObjectURL(blob);
                  const a = document.createElement('a');
                  a.href = url;
                  a.download = `图文报告-${title}-${patient}-${hid}-${ts}.json`;
                  a.click();
                  URL.revokeObjectURL(url);
                  setExportModalOpen(false);
                }}
                className="w-full py-2.5 bg-slate-100 text-slate-700 rounded text-sm font-semibold hover:bg-slate-200 transition-colors"
              >导出 JSON</button>
              <button
                onClick={() => setExportModalOpen(false)}
                className="w-full py-2.5 border border-border text-text-main rounded text-sm font-semibold hover:bg-slate-50 transition-colors"
              >取消</button>
            </div>
          </div>
        </div>
      )}

      {imagePickerOpen && imagePickerTarget && (
        <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
          <div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
            <h3 className="text-lg font-bold text-text-main mb-4">选择图片来源</h3>
            <div className="space-y-3">
              <button
                onClick={() => {
                  const input = document.createElement('input');
                  input.type = 'file';
                  input.accept = 'image/*';
                  input.onchange = (ev) => {
                    const file = (ev.target as HTMLInputElement).files?.[0];
                    if (file) {
                      const reader = new FileReader();
                      reader.onload = (event) => {
                        const src = event.target?.result as string;
                        fillPlaceholderSrc(imagePickerTarget, src);
                        setImagePickerOpen(false);
                        setImagePickerTarget(null);
                      };
                      reader.readAsDataURL(file);
                    }
                  };
                  input.click();
                }}
                className="w-full py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded text-sm font-semibold"
              >本地上传</button>
              <button
                onClick={() => {
                  if (currentUser?.signature) {
                    fillPlaceholderSrc(imagePickerTarget, currentUser.signature);
                  }
                  setImagePickerOpen(false);
                  setImagePickerTarget(null);
                }}
                disabled={!currentUser?.signature}
                className={`w-full py-2 rounded text-sm font-semibold ${currentUser?.signature ? 'bg-slate-100 hover:bg-slate-200 text-slate-700' : 'bg-slate-50 text-slate-400 cursor-not-allowed'}`}
              >我的签名 {!currentUser?.signature && '(未上传)'}</button>
              <div>
                <div className="text-xs text-slate-500 mb-2">系统素材</div>
                <div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto">
                  {imageAssets.map(asset => (
                    <button
                      key={asset.id}
                      onClick={() => { fillPlaceholderSrc(imagePickerTarget, asset.dataUrl); setImagePickerOpen(false); setImagePickerTarget(null); }}
                      className="w-16 h-16 border rounded overflow-hidden hover:ring-2 ring-accent"
                    >
                      <img src={asset.dataUrl} alt={asset.name} className="w-full h-full object-cover" />
                    </button>
                  ))}
                  {imageAssets.length === 0 && <div className="text-xs text-slate-400">暂无素材</div>}
                </div>
              </div>
            </div>
            <div className="mt-5 flex justify-end">
              <button onClick={() => { setImagePickerOpen(false); setImagePickerTarget(null); }} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
            </div>
          </div>
        </div>
      )}

      {/* AI 修改二次确认 Diff 弹窗 */}
      {diffModal && diffModal.isOpen && (
        <div className="fixed inset-0 bg-slate-900/40 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
          <div className="bg-white rounded-2xl w-full max-w-[800px] shadow-2xl flex flex-col max-h-[85vh] overflow-hidden">
            <div className="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
              <div>
                <h3 className="text-lg font-bold text-slate-800 flex items-center gap-2">
                  <Sparkles size={18} className="text-blue-600" />
                  AI 修改确认
                </h3>
                <p className="text-xs text-slate-500 mt-1">您可以直接在右侧面板手动微调 AI 生成的内容。</p>
              </div>
              <button onClick={() => setDiffModal(null)} className="text-slate-400 hover:text-slate-600"><X size={20}/></button>
            </div>
            {(() => {
              const oldText = stripHtml(diffModal.originalHtml);
              const newText = stripHtml(diffModal.newHtml);
              const leftDiffHtml = computeDiffHtml(oldText, newText, 'left');
              const rightDiffHtml = computeDiffHtml(oldText, newText, 'right');
              return (
                <div className="flex-1 overflow-hidden flex gap-4 p-6 bg-slate-100">
                  <div className="flex-1 flex flex-col bg-white border border-red-200 rounded-xl overflow-hidden shadow-sm">
                    <div className="bg-red-50 px-3 py-2 text-xs font-bold text-red-600 border-b border-red-100 uppercase tracking-wider">原始版本</div>
                    <div className="p-4 flex-1 overflow-y-auto opacity-70 cursor-not-allowed custom-scrollbar"
                      dangerouslySetInnerHTML={{ __html: leftDiffHtml }}
                      style={{ fontFamily: 'SimSun, "Microsoft YaHei", serif', fontSize: '12pt', lineHeight: '1.5' }}
                    ></div>
                  </div>
                  <div className="flex-1 flex flex-col bg-white border border-green-400 rounded-xl overflow-hidden shadow-md relative">
                    <div className="bg-green-50 px-3 py-2 text-xs font-bold text-green-700 border-b border-green-200 uppercase tracking-wider flex justify-between">
                      <span>AI 提议版本 (可直接编辑)</span>
                      <span className="text-[10px] bg-green-200 px-1.5 py-0.5 rounded text-green-800">编辑态</span>
                    </div>
                    <div
                      className="p-4 flex-1 overflow-y-auto outline-none custom-scrollbar"
                      contentEditable
                      suppressContentEditableWarning
                      onBlur={(e) => setDiffModal(prev => prev ? { ...prev, newHtml: e.target.innerHTML } : null)}
                      dangerouslySetInnerHTML={{ __html: rightDiffHtml }}
                      style={{ fontFamily: 'SimSun, "Microsoft YaHei", serif', fontSize: '12pt', lineHeight: '1.5' }}
                    ></div>
                  </div>
                </div>
              );
            })()}
            <div className="px-6 py-4 border-t border-slate-100 flex justify-end gap-3 bg-white">
              <button onClick={() => setDiffModal(null)} className="px-6 py-2 rounded-lg text-slate-600 font-medium hover:bg-slate-100">放弃修改</button>
              <button onClick={() => confirmAiInjection(diffModal.newHtml, diffModal.targetId)} className="px-6 py-2 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 shadow-sm flex items-center gap-2">
                <Check size={16} /> 确认并写入报告
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
