/**
 * Full-screen project editor overlay.
 *
 * Pattern: follows ChangeReviewDialog.tsx — raw <div> with fixed inset-0, not Radix Dialog.
 * macOS traffic light padding, inert on background, Escape to close.
 */

import { useCallback, useEffect, useRef, useState } from 'react';

import { api } from '@renderer/api';
import { Button } from '@renderer/components/ui/button';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@renderer/components/ui/dialog';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useEditorKeyboardShortcuts } from '@renderer/hooks/useEditorKeyboardShortcuts';
import { useStore } from '@renderer/store';
import { buildFileAction, buildSelectionAction } from '@renderer/utils/buildSelectionAction';
import { shortcutLabel } from '@renderer/utils/platformKeys';
import { getBasename, getDirname } from '@shared/utils/platformPath';
import {
  AlertTriangle,
  HelpCircle,
  Loader2,
  PanelLeftClose,
  PanelLeftOpen,
  RefreshCw,
  RotateCcw,
  X,
} from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';

import { CodeMirrorEditor } from './CodeMirrorEditor';
import { EditorBinaryState } from './EditorBinaryState';
import { EditorEmptyState } from './EditorEmptyState';
import { EditorErrorBoundary } from './EditorErrorBoundary';
import { EditorErrorState } from './EditorErrorState';
import { EditorFileTree } from './EditorFileTree';
import { EditorSelectionMenu } from './EditorSelectionMenu';
import { EditorShortcutsHelp } from './EditorShortcutsHelp';
import { EditorStatusBar } from './EditorStatusBar';
import { EditorTabBar } from './EditorTabBar';
import { EditorToolbar } from './EditorToolbar';
import { GoToLineDialog } from './GoToLineDialog';
import { MarkdownSplitView } from './MarkdownSplitView';
import { QuickOpenDialog } from './QuickOpenDialog';
import { SearchInFilesPanel } from './SearchInFilesPanel';

import type { MdPreviewMode } from './EditorToolbar';
import type {
  EditorSelectionAction,
  EditorSelectionInfo,
  ReadFileResult,
} from '@shared/types/editor';

// =============================================================================
// Types
// =============================================================================

interface ProjectEditorOverlayProps {
  projectPath: string;
  onClose: () => void;
  /** Called when user triggers an action from the selection menu */
  onEditorAction?: (action: EditorSelectionAction) => void;
}

// =============================================================================
// Component
// =============================================================================

export const ProjectEditorOverlay = ({
  projectPath,
  onClose,
  onEditorAction,
}: ProjectEditorOverlayProps): React.ReactElement => {
  // Data selectors — grouped with useShallow to prevent unnecessary re-renders
  const { activeTabId, openTabs, modifiedFiles, saveErrors, externalChanges, conflictFile } =
    useStore(
      useShallow((s) => ({
        activeTabId: s.editorActiveTabId,
        openTabs: s.editorOpenTabs,
        modifiedFiles: s.editorModifiedFiles,
        saveErrors: s.editorSaveError,
        externalChanges: s.editorExternalChanges,
        conflictFile: s.editorConflictFile,
      }))
    );

  // Actions — stable references in Zustand, no grouping needed
  const openEditor = useStore((s) => s.openEditor);
  const closeEditor = useStore((s) => s.closeEditor);
  const openFile = useStore((s) => s.openFile);
  const closeEditorTab = useStore((s) => s.closeEditorTab);
  const saveFile = useStore((s) => s.saveFile);
  const hasUnsavedChanges = useStore((s) => s.hasUnsavedChanges);
  const saveAllFiles = useStore((s) => s.saveAllFiles);
  const discardChanges = useStore((s) => s.discardChanges);
  const clearExternalChange = useStore((s) => s.clearExternalChange);
  const forceOverwrite = useStore((s) => s.forceOverwrite);
  const resolveConflict = useStore((s) => s.resolveConflict);
  const setFileMtime = useStore((s) => s.setFileMtime);
  const fetchGitStatus = useStore((s) => s.fetchGitStatus);

  const [fileContent, setFileContent] = useState<ReadFileResult | null>(null);
  const [fileLoading, setFileLoading] = useState(false);
  const [fileError, setFileError] = useState<string | null>(null);
  const [cursorLine, setCursorLine] = useState(1);
  const [cursorCol, setCursorCol] = useState(1);

  // Unsaved changes confirmation (overlay close)
  const [showConfirmClose, setShowConfirmClose] = useState(false);
  // Unsaved changes confirmation (single tab close)
  const [confirmCloseTabId, setConfirmCloseTabId] = useState<string | null>(null);
  // Draft recovery banner
  const [draftRecoveredFile, setDraftRecoveredFile] = useState<string | null>(null);
  // Bumped on draft discard to force CodeMirrorEditor remount (fresh state cache)
  const [editorResetKey, setEditorResetKey] = useState(0);
  // Selection action menu
  const [selectionInfo, setSelectionInfo] = useState<EditorSelectionInfo | null>(null);
  const editorContentRef = useRef<HTMLDivElement>(null);
  const [containerRect, setContainerRect] = useState<DOMRect>(() => new DOMRect());

  // Markdown preview state
  const [mdPreviewMode, setMdPreviewMode] = useState<MdPreviewMode>('off');
  const [liveContent, setLiveContent] = useState('');
  const [splitRatio, setSplitRatio] = useState(() => {
    try {
      const stored = localStorage.getItem('editor:mdSplitRatio');
      return stored ? Math.max(0.2, Math.min(0.8, Number(stored))) : 0.5;
    } catch {
      return 0.5;
    }
  });

  // Iter-4: New state
  const [quickOpenVisible, setQuickOpenVisible] = useState(false);
  const [searchPanelVisible, setSearchPanelVisible] = useState(false);
  const [goToLineVisible, setGoToLineVisible] = useState(false);
  const [shortcutsHelpVisible, setShortcutsHelpVisible] = useState(false);
  const [sidebarVisible, setSidebarVisibleRaw] = useState(() => {
    try {
      return localStorage.getItem('editor-sidebar-visible') !== 'false';
    } catch {
      return true;
    }
  });

  const overlayRef = useRef<HTMLDivElement>(null);

  // IPC deduplication: reuse in-flight readFile promise for same path
  const pendingReads = useRef(new Map<string, Promise<ReadFileResult>>());

  // Active tab metadata
  const activeTab = openTabs.find((t) => t.id === activeTabId) ?? null;
  const isMarkdown = activeTab?.language === 'Markdown';

  // Auto-enable split preview for markdown tabs, reset for non-markdown
  useEffect(() => {
    if (isMarkdown) {
      setMdPreviewMode((m) => (m === 'off' ? 'split' : m));
    } else {
      setMdPreviewMode('off');
    }
  }, [isMarkdown, activeTabId]);

  // Persist split ratio
  const handleSplitRatioChange = useCallback((ratio: number) => {
    setSplitRatio(ratio);
    try {
      localStorage.setItem('editor:mdSplitRatio', String(ratio));
    } catch {
      // localStorage unavailable
    }
  }, []);

  const handleLiveContent = useCallback((content: string) => {
    setLiveContent(content);
  }, []);

  const toggleMdSplit = useCallback(() => {
    setMdPreviewMode((m) => (m === 'split' ? 'off' : 'split'));
  }, []);

  const toggleMdPreview = useCallback(() => {
    setMdPreviewMode((m) => (m === 'preview' ? 'off' : 'preview'));
  }, []);

  // Initialize live content when entering preview mode or switching files
  useEffect(() => {
    if (mdPreviewMode !== 'off' && fileContent?.content) {
      setLiveContent(fileContent.content);
    }
  }, [mdPreviewMode, fileContent?.content]);

  // Content for preview: use live content when available, fallback to file content
  const previewContent = liveContent || fileContent?.content || '';

  const loadFileContent = useCallback(
    async (filePath: string) => {
      setFileLoading(true);
      setFileError(null);
      setFileContent(null);

      try {
        const t0 = performance.now();
        let promise = pendingReads.current.get(filePath);
        const wasCached = !!promise;
        if (!promise) {
          promise = api.editor.readFile(filePath);
          pendingReads.current.set(filePath, promise);
          void promise.finally(() => pendingReads.current.delete(filePath));
        }
        const result = await promise;
        const ipcMs = performance.now() - t0;
        console.debug(
          `[perf] loadFileContent: IPC=${ipcMs.toFixed(1)}ms, size=${result.size}, truncated=${result.truncated}, cached=${wasCached}, file=${getBasename(filePath)}`
        );
        setFileContent(result);

        // Track baseline mtime for conflict detection
        if (result.mtimeMs) {
          setFileMtime(filePath, result.mtimeMs);
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setFileError(message);
      } finally {
        setFileLoading(false);
      }
    },
    [setFileMtime]
  );

  // Active tab save error
  const activeSaveError = activeTabId ? (saveErrors[activeTabId] ?? null) : null;

  const pendingRevealFile = useStore((s) => s.editorPendingRevealFile);
  const revealAndOpenFile = useStore((s) => s.revealAndOpenFile);

  // Initialize editor on mount
  useEffect(() => {
    void openEditor(projectPath);
    return () => {
      closeEditor();
    };
  }, [projectPath, openEditor, closeEditor]);

  // Process pending file reveal after editor initializes.
  // Guard: wait until the file tree is actually loaded (not null) and not loading.
  // Without the fileTree check, the effect fires on mount when fileTreeLoading is
  // still at its initial `false` value — before openEditor sets it to `true`.
  const fileTreeLoading = useStore((s) => s.editorFileTreeLoading);
  const fileTreeLoaded = useStore((s) => s.editorFileTree !== null);
  useEffect(() => {
    if (pendingRevealFile && !fileTreeLoading && fileTreeLoaded) {
      void revealAndOpenFile(pendingRevealFile);
    }
  }, [pendingRevealFile, fileTreeLoading, fileTreeLoaded, revealAndOpenFile]);

  // Keep container rect fresh for selection menu positioning (resize, sidebar toggle)
  useEffect(() => {
    const el = editorContentRef.current;
    if (!el) return;
    const updateRect = (): void => setContainerRect(el.getBoundingClientRect());
    updateRect();
    const observer = new ResizeObserver(updateRect);
    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  // Escape to close + F5 to refresh (with dialog guard)
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        // Skip if another handler already consumed this Escape
        // (e.g. CodeMirror search panel close, or React search input onKeyDown)
        if (e.defaultPrevented) return;
        // Don't close overlay if a dialog is open — dialog handles its own Escape
        if (quickOpenVisible || searchPanelVisible || shortcutsHelpVisible) return;
        if (showConfirmClose || confirmCloseTabId) return;
        if (conflictFile) return;

        e.preventDefault();
        handleCloseRequest();
      }

      // F5: Manual refresh (git status + file tree)
      if (e.key === 'F5') {
        e.preventDefault();
        handleManualRefresh();
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- handleCloseRequest and handleManualRefresh are stable callbacks; listing dialog visibility guards as deps is sufficient
  }, [
    quickOpenVisible,
    searchPanelVisible,
    shortcutsHelpVisible,
    showConfirmClose,
    confirmCloseTabId,
    conflictFile,
  ]);

  // Focus trap — focus overlay on mount
  useEffect(() => {
    overlayRef.current?.focus();
  }, []);

  // Load file content when active tab changes
  useEffect(() => {
    // Clear selection menu from previous tab
    setSelectionInfo(null);

    if (!activeTabId) {
      setFileContent(null);
      setFileLoading(false);
      setFileError(null);
      return;
    }

    void loadFileContent(activeTabId);
  }, [activeTabId, loadFileContent]);

  // Clear draft recovery banner when switching tabs
  useEffect(() => {
    if (activeTabId !== draftRecoveredFile) {
      setDraftRecoveredFile(null);
    }
  }, [activeTabId, draftRecoveredFile]);

  const handleFileSelect = useCallback(
    (filePath: string) => {
      openFile(filePath);
    },
    [openFile]
  );

  const handleRetry = useCallback(() => {
    if (activeTabId) {
      void loadFileContent(activeTabId);
    }
  }, [activeTabId, loadFileContent]);

  const handleCursorChange = useCallback((line: number, col: number) => {
    setCursorLine(line);
    setCursorCol(col);
  }, []);

  // --- Overlay close handlers ---

  const handleCloseRequest = useCallback(() => {
    if (hasUnsavedChanges()) {
      setShowConfirmClose(true);
    } else {
      onClose();
    }
  }, [onClose, hasUnsavedChanges]);

  const handleSaveAndClose = useCallback(async () => {
    await saveAllFiles();
    setShowConfirmClose(false);
    onClose();
  }, [saveAllFiles, onClose]);

  const handleDiscardAndClose = useCallback(() => {
    setShowConfirmClose(false);
    onClose();
  }, [onClose]);

  const handleCancelClose = useCallback(() => {
    setShowConfirmClose(false);
  }, []);

  // --- Tab close handlers (with dirty check) ---

  const handleRequestCloseTab = useCallback(
    (tabId: string) => {
      if (modifiedFiles[tabId]) {
        setConfirmCloseTabId(tabId);
      } else {
        closeEditorTab(tabId);
      }
    },
    [modifiedFiles, closeEditorTab]
  );

  // Listen for editor-close-tab custom events from keyboard shortcut hook
  useEffect(() => {
    const handler = (e: Event) => {
      const tabId = (e as CustomEvent).detail as string;
      handleRequestCloseTab(tabId);
    };
    window.addEventListener('editor-close-tab', handler);
    return () => window.removeEventListener('editor-close-tab', handler);
  }, [handleRequestCloseTab]);

  const handleSaveAndCloseTab = useCallback(async () => {
    if (!confirmCloseTabId) return;
    await saveFile(confirmCloseTabId);
    closeEditorTab(confirmCloseTabId);
    setConfirmCloseTabId(null);
  }, [confirmCloseTabId, saveFile, closeEditorTab]);

  const handleDiscardAndCloseTab = useCallback(() => {
    if (!confirmCloseTabId) return;
    closeEditorTab(confirmCloseTabId);
    setConfirmCloseTabId(null);
  }, [confirmCloseTabId, closeEditorTab]);

  const handleCancelCloseTab = useCallback(() => {
    setConfirmCloseTabId(null);
  }, []);

  // --- Draft recovery handlers ---

  const handleDraftRecovered = useCallback((filePath: string) => {
    setDraftRecoveredFile(filePath);
  }, []);

  const handleDiscardDraft = useCallback(() => {
    if (!draftRecoveredFile || !activeTabId) return;
    discardChanges(draftRecoveredFile);
    setDraftRecoveredFile(null);
    setFileContent(null);
    setEditorResetKey((k) => k + 1);
    void loadFileContent(activeTabId);
  }, [draftRecoveredFile, activeTabId, discardChanges, loadFileContent]);

  const handleDismissDraftBanner = useCallback(() => {
    setDraftRecoveredFile(null);
  }, []);

  // --- Iter-5: Conflict handlers ---

  const handleForceOverwrite = useCallback(() => {
    if (!conflictFile) return;
    void forceOverwrite(conflictFile);
  }, [conflictFile, forceOverwrite]);

  const handleCancelConflict = useCallback(() => {
    resolveConflict();
  }, [resolveConflict]);

  // --- Iter-5: External change handlers ---

  const handleReloadExternalChange = useCallback(() => {
    if (!activeTabId) return;
    clearExternalChange(activeTabId);
    discardChanges(activeTabId);
    setFileContent(null);
    setEditorResetKey((k) => k + 1);
    void loadFileContent(activeTabId);
  }, [activeTabId, clearExternalChange, discardChanges, loadFileContent]);

  const handleKeepMine = useCallback(() => {
    if (!activeTabId) return;
    clearExternalChange(activeTabId);
  }, [activeTabId, clearExternalChange]);

  // --- Iter-5: Watcher toggle ---

  // --- Iter-5: Manual refresh (F5) ---

  const handleManualRefresh = useCallback(() => {
    void fetchGitStatus();
  }, [fetchGitStatus]);

  // --- Iter-4: Toggle handlers ---

  const toggleQuickOpen = useCallback(() => {
    setQuickOpenVisible((v) => !v);
  }, []);

  const toggleSearchPanel = useCallback(() => {
    setSearchPanelVisible((v) => !v);
  }, []);

  const toggleGoToLine = useCallback(() => {
    setGoToLineVisible((v) => !v);
  }, []);

  const toggleSidebar = useCallback(() => {
    setSidebarVisibleRaw((v) => {
      const next = !v;
      try {
        localStorage.setItem('editor-sidebar-visible', String(next));
      } catch {
        // localStorage unavailable
      }
      return next;
    });
  }, []);

  // --- Iter-4: Search result selection ---

  const setPendingGoToLine = useStore((s) => s.setPendingGoToLine);

  const handleSearchSelectMatch = useCallback(
    (filePath: string, line: number) => {
      setPendingGoToLine(line);
      openFile(filePath);
    },
    [openFile, setPendingGoToLine]
  );

  // --- Keyboard shortcuts ---

  useEditorKeyboardShortcuts({
    onToggleQuickOpen: toggleQuickOpen,
    onToggleSearchPanel: toggleSearchPanel,
    onToggleGoToLine: toggleGoToLine,
    onToggleSidebar: toggleSidebar,
    onClose: handleCloseRequest,
    onToggleMdSplit: isMarkdown ? toggleMdSplit : undefined,
    onToggleMdPreview: isMarkdown ? toggleMdPreview : undefined,
  });

  const projectName = getBasename(projectPath) || projectPath;

  return (
    <div
      ref={overlayRef}
      className="fixed inset-0 z-50 m-0 flex h-screen w-screen flex-col overflow-hidden border-0 bg-surface"
      tabIndex={-1}
      role="dialog"
      aria-modal="true"
      aria-label="项目编辑器"
    >
      {/* Header */}
      <div
        className="flex h-10 shrink-0 items-center justify-between border-b border-border px-3"
        style={{ paddingLeft: 'var(--macos-traffic-light-padding-left, 72px)' }}
      >
        <div className="flex items-center gap-2 text-sm text-text-secondary">
          <span className="font-medium text-text">{projectName}</span>
          <span className="text-text-muted">{projectPath}</span>
        </div>
        <div className="flex items-center gap-1">
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                variant="ghost"
                size="icon"
                className="size-7 text-text-muted"
                onClick={handleManualRefresh}
                aria-label="刷新（F5）"
              >
                <RefreshCw className="size-4" />
              </Button>
            </TooltipTrigger>
            <TooltipContent side="bottom">刷新 Git 状态（F5）</TooltipContent>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                variant="ghost"
                size="icon"
                className="size-7 text-text-muted"
                onClick={() => setShortcutsHelpVisible(true)}
                aria-label="快捷键"
              >
                <HelpCircle className="size-4" />
              </Button>
            </TooltipTrigger>
            <TooltipContent side="bottom">快捷键</TooltipContent>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                variant="ghost"
                size="icon"
                className="size-7 text-text-muted"
                onClick={handleCloseRequest}
                aria-label="关闭编辑器"
              >
                <X className="size-4" />
              </Button>
            </TooltipTrigger>
            <TooltipContent side="bottom">关闭编辑器（Esc）</TooltipContent>
          </Tooltip>
        </div>
      </div>

      {/* Main content */}
      <div className="flex flex-1 overflow-hidden">
        {/* Search in files panel (replaces sidebar when visible) */}
        {searchPanelVisible && (
          <div className="w-72 shrink-0">
            <SearchInFilesPanel
              projectPath={projectPath}
              onClose={() => setSearchPanelVisible(false)}
              onSelectMatch={handleSearchSelectMatch}
            />
          </div>
        )}

        {/* File tree sidebar */}
        {sidebarVisible && !searchPanelVisible && (
          <div className="flex w-60 shrink-0 flex-col border-r border-border bg-surface-sidebar">
            <div className="flex items-center justify-between border-b border-border px-2 py-1">
              <span className="text-[10px] font-medium uppercase tracking-wider text-text-muted">
                资源管理器
              </span>
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    variant="ghost"
                    size="icon"
                    className="size-6 text-text-muted"
                    onClick={toggleSidebar}
                    aria-label="隐藏侧边栏"
                  >
                    <PanelLeftClose className="size-3.5" />
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom">
                  隐藏侧边栏（{shortcutLabel('⌘ B', 'Ctrl+B')}）
                </TooltipContent>
              </Tooltip>
            </div>
            <div className="flex-1 overflow-hidden">
              <EditorFileTree
                selectedFilePath={activeTabId}
                onFileSelect={handleFileSelect}
                onCreateTask={
                  onEditorAction
                    ? (filePath: string) =>
                        onEditorAction(buildFileAction('createTask', filePath, projectPath))
                    : undefined
                }
                onSendMessage={
                  onEditorAction
                    ? (filePath: string) =>
                        onEditorAction(buildFileAction('sendMessage', filePath, projectPath))
                    : undefined
                }
              />
            </div>
          </div>
        )}

        {/* Sidebar toggle (when hidden) */}
        {!sidebarVisible && !searchPanelVisible && (
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                variant="ghost"
                className="flex h-full w-6 shrink-0 items-start justify-center rounded-none border-r border-border bg-surface-sidebar pt-2 text-text-muted"
                onClick={toggleSidebar}
                aria-label="显示侧边栏"
              >
                <PanelLeftOpen className="size-3.5" />
              </Button>
            </TooltipTrigger>
            <TooltipContent side="right">
              显示侧边栏（{shortcutLabel('⌘ B', 'Ctrl+B')}）
            </TooltipContent>
          </Tooltip>
        )}

        {/* Editor area */}
        <div className="flex flex-1 flex-col overflow-hidden">
          {/* Tab bar */}
          <EditorTabBar onRequestCloseTab={handleRequestCloseTab} />

          {/* Toolbar */}
          <EditorToolbar
            isMarkdown={isMarkdown}
            mdPreviewMode={mdPreviewMode}
            onToggleSplit={toggleMdSplit}
            onToggleFullPreview={toggleMdPreview}
          />

          {/* Draft recovery banner */}
          {draftRecoveredFile && activeTabId === draftRecoveredFile && (
            <div
              className="flex shrink-0 items-center gap-2 border-b px-3 py-1.5 text-xs"
              style={{
                backgroundColor: 'var(--warning-bg)',
                borderColor: 'var(--warning-border)',
                color: 'var(--warning-text)',
              }}
            >
              <RotateCcw className="size-3.5 shrink-0" />
              <span>已从上次会话恢复未保存的更改。</span>
              <Button
                variant="ghost"
                size="sm"
                className="ml-auto h-auto px-2 py-0.5"
                onClick={handleDismissDraftBanner}
              >
                保留
              </Button>
              <Button
                variant="destructive"
                size="sm"
                className="h-auto px-2 py-0.5"
                onClick={handleDiscardDraft}
              >
                丢弃
              </Button>
            </div>
          )}

          {/* Save error banner */}
          {activeSaveError && (
            <div className="flex shrink-0 items-center gap-2 border-b border-red-500/30 bg-red-500/10 px-3 py-1.5 text-xs text-red-300">
              <AlertTriangle className="size-3.5 shrink-0" />
              <span className="truncate">保存失败：{activeSaveError}</span>
              <Button
                variant="ghost"
                size="sm"
                className="ml-auto h-auto shrink-0 px-2 py-0.5"
                onClick={() => activeTabId && void saveFile(activeTabId)}
              >
                重试
              </Button>
            </div>
          )}

          {/* External change banner */}
          {activeTabId && externalChanges[activeTabId] && (
            <div className="flex shrink-0 items-center gap-2 border-b border-blue-400/30 bg-blue-100/50 px-3 py-1.5 text-xs text-blue-700 dark:border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-300">
              <RefreshCw className="size-3.5 shrink-0" />
              <span>
                {externalChanges[activeTabId] === 'delete'
                  ? '文件已从磁盘中删除。'
                  : '文件已在磁盘上被修改。'}
              </span>
              {externalChanges[activeTabId] === 'delete' ? (
                <Button
                  variant="ghost"
                  size="sm"
                  className="ml-auto h-auto px-2 py-0.5"
                  onClick={() => closeEditorTab(activeTabId)}
                >
                  关闭标签页
                </Button>
              ) : (
                <>
                  <Button
                    variant="ghost"
                    size="sm"
                    className="ml-auto h-auto px-2 py-0.5"
                    onClick={handleReloadExternalChange}
                  >
                    重新加载
                  </Button>
                  <Button
                    variant="ghost"
                    size="sm"
                    className="h-auto px-2 py-0.5"
                    onClick={handleKeepMine}
                  >
                    保留我的版本
                  </Button>
                </>
              )}
            </div>
          )}

          {/* Editor content */}
          <div ref={editorContentRef} className="relative flex-1 overflow-hidden">
            {fileLoading && (
              <div className="flex h-full items-center justify-center text-text-muted">
                <Loader2 className="size-5 animate-spin" />
              </div>
            )}

            {fileError && <EditorErrorState error={fileError} onRetry={handleRetry} />}

            {fileContent?.isBinary && activeTabId && (
              <EditorBinaryState filePath={activeTabId} size={fileContent.size} />
            )}

            {fileContent && !fileContent.isBinary && activeTabId && (
              <div className="flex h-full">
                {/* Code editor — always mounted, hidden via display:none in preview mode */}
                <div
                  className="h-full overflow-hidden"
                  style={{
                    display: mdPreviewMode === 'preview' ? 'none' : 'block',
                    width: mdPreviewMode === 'split' ? `${splitRatio * 100}%` : '100%',
                  }}
                >
                  <EditorErrorBoundary filePath={activeTabId} onRetry={handleRetry}>
                    <CodeMirrorEditor
                      key={`${activeTabId}-${editorResetKey}`}
                      filePath={activeTabId}
                      content={fileContent.content}
                      fileName={getBasename(activeTabId) || 'file'}
                      mtimeMs={fileContent.mtimeMs}
                      onCursorChange={handleCursorChange}
                      onDraftRecovered={handleDraftRecovered}
                      onSelectionChange={setSelectionInfo}
                      onDocChange={mdPreviewMode !== 'off' ? handleLiveContent : undefined}
                    />
                  </EditorErrorBoundary>
                </div>

                {/* Resize handle + Preview pane */}
                {mdPreviewMode !== 'off' && (
                  <MarkdownSplitView
                    content={previewContent}
                    mode={mdPreviewMode}
                    splitRatio={splitRatio}
                    onSplitRatioChange={handleSplitRatioChange}
                    viewKey={activeTabId}
                    baseDir={activeTabId ? getDirname(activeTabId) : undefined}
                  />
                )}
              </div>
            )}

            {!fileLoading && !fileError && !fileContent && !activeTabId && <EditorEmptyState />}

            {/* Selection action menu */}
            {selectionInfo && onEditorAction && (
              <EditorSelectionMenu
                info={selectionInfo}
                containerRect={containerRect}
                onSendMessage={() => {
                  onEditorAction(buildSelectionAction('sendMessage', selectionInfo));
                  setSelectionInfo(null);
                }}
                onCreateTask={() => {
                  onEditorAction(buildSelectionAction('createTask', selectionInfo));
                  setSelectionInfo(null);
                }}
              />
            )}
          </div>

          {/* Status bar */}
          {activeTab && (
            <EditorStatusBar line={cursorLine} col={cursorCol} language={activeTab.language} />
          )}
        </div>
      </div>

      {/* Quick Open dialog */}
      {quickOpenVisible && (
        <QuickOpenDialog
          onClose={() => setQuickOpenVisible(false)}
          onSelectFile={handleFileSelect}
        />
      )}

      {/* Go to Line dialog */}
      {goToLineVisible && <GoToLineDialog onClose={() => setGoToLineVisible(false)} />}

      {/* Shortcuts help modal */}
      {shortcutsHelpVisible && (
        <EditorShortcutsHelp onClose={() => setShortcutsHelpVisible(false)} />
      )}

      {/* Unsaved changes confirmation dialog — overlay close */}
      <Dialog open={showConfirmClose} onOpenChange={(open) => !open && handleCancelClose()}>
        <DialogContent className="w-96 max-w-96">
          <DialogHeader>
            <DialogTitle className="text-sm">未保存的更改</DialogTitle>
            <DialogDescription>你有未保存的更改。要如何处理？</DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="ghost" size="sm" onClick={handleCancelClose}>
              取消
            </Button>
            <Button variant="destructive" size="sm" onClick={handleDiscardAndClose}>
              丢弃并关闭
            </Button>
            <Button size="sm" onClick={() => void handleSaveAndClose()}>
              全部保存并关闭
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* Save conflict dialog */}
      <Dialog open={!!conflictFile} onOpenChange={(open) => !open && handleCancelConflict()}>
        <DialogContent className="w-96 max-w-96">
          <DialogHeader>
            <DialogTitle className="text-sm">保存冲突</DialogTitle>
            <DialogDescription>该文件在打开后被外部修改。是否用你的更改覆盖？</DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="ghost" size="sm" onClick={handleCancelConflict}>
              取消
            </Button>
            <Button variant="destructive" size="sm" onClick={handleForceOverwrite}>
              覆盖
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* Unsaved changes confirmation dialog — single tab close */}
      <Dialog open={!!confirmCloseTabId} onOpenChange={(open) => !open && handleCancelCloseTab()}>
        <DialogContent className="w-96 max-w-96">
          <DialogHeader>
            <DialogTitle className="text-sm">未保存的更改</DialogTitle>
            <DialogDescription>此文件有未保存的更改。要如何处理？</DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="ghost" size="sm" onClick={handleCancelCloseTab}>
              取消
            </Button>
            <Button variant="destructive" size="sm" onClick={handleDiscardAndCloseTab}>
              丢弃
            </Button>
            <Button size="sm" onClick={() => void handleSaveAndCloseTab()}>
              保存
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
};
