import { useState, useEffect, useLayoutEffect, useCallback, useRef, useMemo } from "react";
import { open } from "@tauri-apps/plugin-dialog";
import { openUrl } from "@tauri-apps/plugin-opener";
import { readTextFile } from "@tauri-apps/plugin-fs";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { getCurrentWebview } from "@tauri-apps/api/webview";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import MarkdownIt from "markdown-it";
import hljs from "highlight.js";
import { motion, AnimatePresence } from "framer-motion";
import TaskLists from "markdown-it-task-lists";
import sup from "markdown-it-sup";
import sub from "markdown-it-sub";
import mark from "markdown-it-mark";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import "overlayscrollbars/overlayscrollbars.css";
import {
  Minus, Plus, Square, X, FileText,
  ChevronLeft, ChevronRight, FolderOpen, FileDown
} from "lucide-react";

interface Tab { id: string; title: string; content: string; path: string; }
interface Heading { id: string; text: string; level: number; }

const md: MarkdownIt = new MarkdownIt({
  html: true,
  breaks: true,
  linkify: true,
  typographer: true,
  highlight: function(str: string, lang: string): string {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return '<pre class="hljs"><code>' + hljs.highlight(str, { language: lang, ignoreIllegals: true }).value + '</code></pre>';
      } catch { }
    }
    return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
  }
}).use(TaskLists).use(sup).use(sub).use(mark);

const materialEase = [0.4, 0, 0.2, 1] as const;
const smoothTransition = { duration: 0.2, ease: materialEase };

// Saved selection range - set in mousedown (before browser clears it), read in contextmenu handler
let _savedSelectionRange: Range | null = null;

// Reusable kinetic scroll + iOS overscroll setup for any scrollable area
function setupKineticScroll(
  viewport: HTMLElement,
  contentEl: HTMLElement,
  kineticDragRef: { current: boolean }
): () => void {
  const DRAG_THRESHOLD = 5;
  let isTracking = false;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let startScrollTop = 0;
  let velocityY = 0;
  let lastY = 0;
  let lastTime = 0;
  let momentumRaf: number | null = null;
  let springRaf: number | null = null;
  let overscrollOffset = 0;

  const cancelAnimations = () => {
    if (momentumRaf) { cancelAnimationFrame(momentumRaf); momentumRaf = null; }
    if (springRaf) { cancelAnimationFrame(springRaf); springRaf = null; }
  };

  const rubberBand = (offset: number, dimension: number): number => {
    const c = 0.55;
    const x = Math.abs(offset);
    return Math.sign(offset) * (1.0 - (1.0 / ((x * c / dimension) + 1.0))) * dimension;
  };

  const setOverscroll = (offset: number) => {
    overscrollOffset = offset;
    contentEl.style.transform = offset !== 0 ? `translateY(${offset}px)` : '';
    contentEl.style.transition = '';
  };

  const springBack = () => {
    cancelAnimations();
    let position = overscrollOffset;
    let velocity = 0;
    const stiffness = 60;
    const damping = 14;
    const dt = 1 / 60;

    const animate = () => {
      const acceleration = -stiffness * position - damping * velocity;
      velocity += acceleration * dt;
      position += velocity * dt;

      if (Math.abs(position) < 0.5 && Math.abs(velocity) < 0.5) {
        setOverscroll(0);
        return;
      }
      setOverscroll(position);
      springRaf = requestAnimationFrame(animate);
    };
    springRaf = requestAnimationFrame(animate);
  };

  const applyMomentum = () => {
    cancelAnimations();
    let v = velocityY;
    const friction = 0.99;

    const animate = () => {
      v *= friction;
      if (Math.abs(v) < 0.05) return;

      const desiredTop = viewport.scrollTop - v;

      // Top edge
      if (desiredTop < 0) {
        viewport.scrollTop = 0;
        const stretch = rubberBand(Math.abs(v) * 10, viewport.clientHeight);
        setOverscroll(Math.abs(stretch));
        springBack();
        return;
      }

      // Set scroll and let browser clamp to the real maximum
      viewport.scrollTop = desiredTop;
      const actualTop = viewport.scrollTop;

      // Bottom edge: browser clamped scrollTop below what we asked for
      if (desiredTop - actualTop > 1) {
        const stretch = rubberBand(Math.abs(v) * 10, viewport.clientHeight);
        setOverscroll(-Math.abs(stretch));
        springBack();
        return;
      }

      momentumRaf = requestAnimationFrame(animate);
    };
    momentumRaf = requestAnimationFrame(animate);
  };

  const onMouseDown = (e: MouseEvent) => {
    if (e.button !== 2) return;
    const target = e.target as HTMLElement;
    if (target.closest('.os-scrollbar')) return;

    // If text is selected, don't start kinetic scroll
    const sel = window.getSelection();
    if (sel && sel.toString().length > 0) return;

    cancelAnimations();
    if (overscrollOffset !== 0) setOverscroll(0);

    isTracking = true;
    isDragging = false;
    startX = e.clientX;
    startY = e.clientY;
    startScrollTop = viewport.scrollTop;
    lastY = e.clientY;
    lastTime = performance.now();
    velocityY = 0;
  };

  const onMouseMove = (e: MouseEvent) => {
    if (!isTracking) return;

    if (!isDragging) {
      const dx = e.clientX - startX;
      const dy = e.clientY - startY;
      if (Math.sqrt(dx * dx + dy * dy) < DRAG_THRESHOLD) return;
      isDragging = true;
      document.body.style.cursor = 'grabbing';
      document.body.style.userSelect = 'none';
    }

    e.preventDefault();

    const now = performance.now();
    const dt = now - lastTime;
    const dy = e.clientY - lastY;

    if (dt > 0) {
      velocityY = velocityY * 0.85 + (dy / dt) * 16 * 0.15;
    }

    lastY = e.clientY;
    lastTime = now;

    const scrollDelta = startY - e.clientY;
    const desiredTop = startScrollTop + scrollDelta;

    // Top edge
    if (desiredTop < 0) {
      viewport.scrollTop = 0;
      setOverscroll(rubberBand(Math.abs(desiredTop), viewport.clientHeight));
    } else {
      // Set scroll and let browser clamp
      viewport.scrollTop = desiredTop;
      const actualTop = viewport.scrollTop;

      // Bottom edge: browser clamped
      if (desiredTop - actualTop > 1) {
        setOverscroll(-rubberBand(desiredTop - actualTop, viewport.clientHeight));
      } else {
        if (overscrollOffset !== 0) setOverscroll(0);
      }
    }
  };

  const onMouseUp = () => {
    if (!isTracking) return;
    isTracking = false;

    if (isDragging) {
      isDragging = false;
      kineticDragRef.current = true;
      document.body.style.cursor = '';
      document.body.style.userSelect = '';

      if (overscrollOffset !== 0) {
        springBack();
      } else if (Math.abs(velocityY) > 0.5) {
        applyMomentum();
      }
    }
  };

  viewport.addEventListener('mousedown', onMouseDown);
  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);

  return () => {
    cancelAnimations();
    viewport.removeEventListener('mousedown', onMouseDown);
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
    document.body.style.cursor = '';
    document.body.style.userSelect = '';
    contentEl.style.transform = '';
  };
}

function App() {
  const [tabs, setTabs] = useState<Tab[]>([]);
  const [activeTabId, setActiveTabId] = useState<string | null>(null);
  const [showSidebar, setShowSidebar] = useState(false);
  const [showSearch, setShowSearch] = useState(false);
  const [focusMode, setFocusMode] = useState(true);
  const [isDraggingOver, setIsDraggingOver] = useState(false);
  const [searchQuery, setSearchQuery] = useState("");
  const [searchMatches, setSearchMatches] = useState<number[]>([]);
  const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
  const [headings, setHeadings] = useState<Heading[]>([]);
  const [menuOpen, setMenuOpen] = useState<string | null>(null);
  const [menuFocusIndex, setMenuFocusIndex] = useState(0);
  const menuItems = ['file', 'view', 'help'] as const;
  const [showShortcutsModal, setShowShortcutsModal] = useState(false);
  const [showAboutModal, setShowAboutModal] = useState(false);
  const [zoom, setZoom] = useState(100);
  const [uiZoom, setUiZoom] = useState(() => {
    const saved = localStorage.getItem('vesper-ui-zoom');
    return saved ? parseInt(saved, 10) : 100;
  });
  const [contentWidth, setContentWidth] = useState(680);
  const [sidebarWidth, setSidebarWidth] = useState(() => {
    const saved = localStorage.getItem('vesper-sidebar-width');
    return saved ? parseInt(saved, 10) : 220;
  });
  const [searchTriggerCount, setSearchTriggerCount] = useState(0);
  const [resizeStartState, setResizeStartState] = useState<{ startX: number; startWidth: number } | null>(null);
  const [contextMenu, setContextMenu] = useState<{ x: number; y: number; selectedText: string } | null>(null);
  const [selectionRects, setSelectionRects] = useState<{ left: number; top: number; width: number; height: number }[]>([]);
  const [tabFocusIndex, setTabFocusIndex] = useState(0);

  const contentRef = useRef<HTMLDivElement>(null);
  const sidebarRef = useRef<HTMLDivElement>(null);
  const searchInputRef = useRef<HTMLInputElement>(null);
  const menuItemRefs = useRef<{ [key: string]: HTMLButtonElement | null }>({});
  const kineticDragActiveRef = useRef(false);
  const contextMenuRef = useRef<HTMLDivElement>(null);
  const tabScrollRef = useRef<HTMLDivElement>(null);
  const tabRefs = useRef<(HTMLDivElement | null)[]>([]);
  const [tabScrollState, setTabScrollState] = useState({ canLeft: false, canRight: false });
  const tabsRef = useRef(tabs);
  tabsRef.current = tabs;

  const [appWindow, setAppWindow] = useState<ReturnType<typeof getCurrentWindow> | null>(null);
  const activeTab = tabs.find(t => t.id === activeTabId) || null;

  useEffect(() => {
    setAppWindow(getCurrentWindow());
  }, []);

  // Capture-phase mousedown: save selection range before browser clears it
  useEffect(() => {
    const saveSelection = (e: MouseEvent) => {
      if (e.button === 2) {
        const sel = window.getSelection();
        if (sel && sel.rangeCount > 0 && sel.toString().length > 0) {
          _savedSelectionRange = sel.getRangeAt(0).cloneRange();
        } else {
          _savedSelectionRange = null;
        }
      }
    };
    document.addEventListener('mousedown', saveSelection, true);
    return () => document.removeEventListener('mousedown', saveSelection, true);
  }, []);

  useEffect(() => {
    const handleContextMenu = (e: MouseEvent) => {
      e.preventDefault();
      if (kineticDragActiveRef.current) {
        kineticDragActiveRef.current = false;
        return;
      }
      if (activeTab) {
        const selectedText = window.getSelection()?.toString() || '';
        const zf = uiZoom / 100;
        const x = e.clientX / zf;
        const y = e.clientY / zf;

        // Build selection overlay rects from saved range (before browser cleared it)
        let rects: { left: number; top: number; width: number; height: number }[] = [];
        if (selectedText && _savedSelectionRange) {
          const clientRects = _savedSelectionRange.getClientRects();
          rects = Array.from(clientRects).map(r => ({
            left: r.left / zf, top: r.top / zf, width: r.width / zf, height: r.height / zf
          }));
        }

        setContextMenu({ x, y, selectedText });
        setSelectionRects(rects);
      }
    };
    const handleClick = () => setContextMenu(null);
    document.addEventListener('contextmenu', handleContextMenu);
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('contextmenu', handleContextMenu);
      document.removeEventListener('click', handleClick);
    };
  }, [activeTab, uiZoom]);

  // Open external links in default OS browser
  useEffect(() => {
    const handleLinkClick = (e: MouseEvent) => {
      const anchor = (e.target as HTMLElement).closest('a[href]') as HTMLAnchorElement | null;
      if (!anchor) return;
      const href = anchor.getAttribute('href');
      if (href && /^https?:\/\//.test(href)) {
        e.preventDefault();
        openUrl(href);
      }
    };
    document.addEventListener('click', handleLinkClick);
    return () => document.removeEventListener('click', handleLinkClick);
  }, []);

  // Clear selection overlay when context menu closes
  useEffect(() => {
    if (!contextMenu) setSelectionRects(prev => prev.length > 0 ? [] : prev);
  }, [contextMenu]);

  // Clamp context menu within window bounds after render
  useLayoutEffect(() => {
    const menu = contextMenuRef.current;
    if (!menu || !contextMenu) return;
    const zf = uiZoom / 100;
    // Menu rect is in viewport pixels (already scaled by zoom)
    const rect = menu.getBoundingClientRect();
    // Convert menu dimensions to zoomed coordinate space
    const menuW = rect.width / zf;
    const menuH = rect.height / zf;
    // Window dimensions in zoomed coordinate space
    const winW = window.innerWidth / zf;
    const winH = window.innerHeight / zf;

    let x = contextMenu.x;
    let y = contextMenu.y;

    if (x + menuW > winW) x = winW - menuW;
    if (y + menuH > winH) y = winH - menuH;
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    menu.style.left = `${x}px`;
    menu.style.top = `${y}px`;
  }, [contextMenu, uiZoom]);

  const parseHeadings = useCallback((content: string) => {
    const headingRegex = /^(#{1,6})\s+(.+)$/gm;
    const parsed: Heading[] = [];
    let match;
    while ((match = headingRegex.exec(content))) {
      parsed.push({ id: match[2].toLowerCase().replace(/[^\w]+/g, '-'), text: match[2], level: match[1].length });
    }
    setHeadings(parsed);
  }, []);

  // Render markdown to HTML - memoized so it only re-runs when content changes
  const renderedHtml = useMemo(() => {
    if (!activeTab) return '';
    return md.render(activeTab.content);
  }, [activeTab]);

  // Inject search highlights into the HTML string (survives React re-renders)
  const { highlightedHtml, highlightCount } = useMemo(() => {
    if (!searchQuery || !renderedHtml) return { highlightedHtml: renderedHtml, highlightCount: 0 };

    const escaped = searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const regex = new RegExp(escaped, 'gi');
    let count = 0;

    // Split on HTML tags, only highlight text segments
    const parts = renderedHtml.split(/(<[^>]*>)/);
    const highlighted = parts.map(part => {
      if (part.startsWith('<')) return part;
      return part.replace(regex, (match) => {
        const idx = count++;
        return `<span class="search-highlight" data-match-index="${idx}">${match}</span>`;
      });
    }).join('');

    return { highlightedHtml: highlighted, highlightCount: count };
  }, [renderedHtml, searchQuery]);

  // Sync highlight count with search matches state
  useEffect(() => {
    setSearchMatches(Array.from({ length: highlightCount }, (_, i) => i));
    if (highlightCount === 0) setCurrentMatchIndex(0);
  }, [highlightCount]);

  // Scroll to active match and toggle active class
  useEffect(() => {
    const container = document.querySelector('.markdown-content');
    if (!container || highlightCount === 0) return;

    container.querySelectorAll('.search-highlight-active').forEach(el => {
      el.classList.remove('search-highlight-active');
    });

    const active = container.querySelector(`[data-match-index="${currentMatchIndex}"]`);
    if (active) {
      active.classList.add('search-highlight-active');
      active.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
  }, [currentMatchIndex, highlightCount, searchTriggerCount]);

  const updateTabScrollState = useCallback(() => {
    const el = tabScrollRef.current;
    if (!el) { setTabScrollState({ canLeft: false, canRight: false }); return; }
    setTabScrollState({
      canLeft: el.scrollLeft > 0,
      canRight: el.scrollLeft < el.scrollWidth - el.clientWidth - 1,
    });
  }, []);

  const tabScrollInterval = useRef<ReturnType<typeof setInterval> | null>(null);

  const startScrollingTabs = useCallback((dir: 'left' | 'right') => {
    const el = tabScrollRef.current;
    if (!el) return;
    const step = dir === 'left' ? -8 : 8;
    el.scrollBy({ left: step });
    updateTabScrollState();
    tabScrollInterval.current = setInterval(() => {
      el.scrollBy({ left: step });
      updateTabScrollState();
    }, 16);
  }, [updateTabScrollState]);

  const stopScrollingTabs = useCallback(() => {
    if (tabScrollInterval.current) {
      clearInterval(tabScrollInterval.current);
      tabScrollInterval.current = null;
    }
  }, []);

  const scrollTabsToEnd = useCallback(() => {
    const el = tabScrollRef.current;
    if (!el) return;
    requestAnimationFrame(() => {
      el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
      setTimeout(updateTabScrollState, 300);
    });
  }, [updateTabScrollState]);

  const scrollToTabIndex = useCallback((index: number) => {
    const el = tabScrollRef.current;
    if (!el) return;
    requestAnimationFrame(() => {
      const tab = el.children[0]?.children[index] as HTMLElement | undefined;
      if (tab) tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
      setTimeout(updateTabScrollState, 300);
    });
  }, [updateTabScrollState]);

  // Tauri native drag-and-drop (bypasses browser event interception)
  useEffect(() => {
    let unlisten: (() => void) | undefined;
    getCurrentWebview().onDragDropEvent(async (event) => {
      if (event.payload.type === 'enter') {
        setIsDraggingOver(true);
      } else if (event.payload.type === 'leave') {
        setIsDraggingOver(false);
      } else if (event.payload.type === 'drop') {
        setIsDraggingOver(false);
        const paths = event.payload.paths;
        const mdPath = paths.find((p: string) => p.endsWith('.md') || p.endsWith('.markdown') || p.endsWith('.txt'));
        if (mdPath) {
          try {
            const existing = tabsRef.current.find(t => t.path === mdPath);
            if (existing) {
              setActiveTabId(existing.id);
              parseHeadings(existing.content);
              scrollToTabIndex(tabsRef.current.indexOf(existing));
            } else {
              const content = await readTextFile(mdPath);
              const title = mdPath.split(/[/\\]/).pop() || 'Untitled';
              const newTab: Tab = { id: Date.now().toString(), title, content, path: mdPath };
              setTabs(prev => [...prev, newTab]);
              setActiveTabId(newTab.id);
              parseHeadings(content);
              scrollTabsToEnd();
            }
          } catch (err) {
            console.error('Failed to read dropped file:', err);
          }
        }
      }
    }).then(fn => { unlisten = fn; });
    return () => { unlisten?.(); };
  }, [parseHeadings]);

  // Handle file opened via OS file association (e.g. double-clicking a .md file)
  useEffect(() => {
    invoke<{ path: string; content: string } | null>('get_cli_file').then(result => {
      if (!result) return;
      const title = result.path.split(/[/\\]/).pop() || 'Untitled';
      const newTab: Tab = { id: Date.now().toString(), title, content: result.content, path: result.path };
      setTabs(prev => [...prev, newTab]);
      setActiveTabId(newTab.id);
      parseHeadings(result.content);
    }).catch(err => console.error('Failed to open file from CLI args:', err));
  }, []);

  // Handle files opened from a second instance (single-instance plugin forwards them here)
  useEffect(() => {
    let unlisten: (() => void) | undefined;
    listen<{ path: string; content: string }>('open-file', (event) => {
      const { path: filePath, content } = event.payload;
      const existing = tabsRef.current.find(t => t.path === filePath);
      if (existing) {
        setActiveTabId(existing.id);
        parseHeadings(existing.content);
      } else {
        const title = filePath.split(/[/\\]/).pop() || 'Untitled';
        const newTab: Tab = { id: Date.now().toString(), title, content, path: filePath };
        setTabs(prev => [...prev, newTab]);
        setActiveTabId(newTab.id);
        parseHeadings(content);
      }
    }).then(fn => { unlisten = fn; });
    return () => { unlisten?.(); };
  }, [parseHeadings]);

  const handleOpenDialog = useCallback(async () => {
    try {
      const selected = await open({ multiple: false, filters: [{ name: 'Markdown', extensions: ['md', 'markdown', 'txt'] }] });
      if (selected) {
        const existing = tabsRef.current.find(t => t.path === selected);
        if (existing) {
          setActiveTabId(existing.id);
          parseHeadings(existing.content);
          scrollToTabIndex(tabsRef.current.indexOf(existing));
        } else {
          const content = await readTextFile(selected);
          const newTab: Tab = { id: Date.now().toString(), title: selected.split(/[/\\]/).pop() || 'Untitled', content, path: selected };
          setTabs(prev => [...prev, newTab]);
          setActiveTabId(newTab.id);
          parseHeadings(content);
          scrollTabsToEnd();
        }
      }
    } catch (err) { console.error('Failed to open file:', err); }
  }, [parseHeadings, scrollTabsToEnd, scrollToTabIndex]);

  const closeTab = useCallback((id: string) => {
    setTabs(prev => {
      const newTabs = prev.filter(t => t.id !== id);
      if (activeTabId === id) {
        const idx = prev.findIndex(t => t.id === id);
        const nextTab = newTabs[idx] || newTabs[idx - 1];
        setActiveTabId(nextTab?.id || null);
        if (nextTab) parseHeadings(nextTab.content); else setHeadings([]);
      }
      return newTabs;
    });
  }, [activeTabId, parseHeadings]);

  // Track tab scroll state + mouse wheel horizontal scroll
  useEffect(() => {
    updateTabScrollState();
    const el = tabScrollRef.current;
    if (!el) return;
    const onScroll = () => updateTabScrollState();
    const onWheel = (e: WheelEvent) => {
      if (e.deltaY !== 0) {
        e.preventDefault();
        el.scrollBy({ left: e.deltaY });
        updateTabScrollState();
      }
    };
    el.addEventListener('scroll', onScroll, { passive: true });
    el.addEventListener('wheel', onWheel, { passive: false });
    window.addEventListener('resize', onScroll);
    return () => { el.removeEventListener('scroll', onScroll); el.removeEventListener('wheel', onWheel); window.removeEventListener('resize', onScroll); };
  }, [tabs.length, updateTabScrollState]);

  const closeWindow = useCallback(async () => { await appWindow?.close(); }, [appWindow]);

  const scrollToHeading = useCallback((text: string) => {
    const allHeadings = document.querySelectorAll('.markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content h5, .markdown-content h6');
    const target = Array.from(allHeadings).find(h => h.textContent?.trim() === text);
    if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }, []);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.ctrlKey || e.metaKey) {
        if (e.key === 'o') { e.preventDefault(); handleOpenDialog(); }
        else if (e.key === 'w') { e.preventDefault(); if (activeTabId) closeTab(activeTabId); }
        else if (e.key === 'q') { e.preventDefault(); closeWindow(); }
        else if (e.key === 'f') { e.preventDefault(); setShowSearch(s => !s); }
        else if (e.shiftKey && e.key === 'S') { e.preventDefault(); setShowSidebar(s => !s); }
      } else if (e.key === 'F11') { e.preventDefault(); setFocusMode(f => !f); }
      else if (e.key === 'Escape') { setShowSearch(false); setShowSidebar(false); }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [handleOpenDialog, closeTab, activeTabId, closeWindow]);

  useEffect(() => {
    const handleWheel = (e: WheelEvent) => {
      if (e.ctrlKey) {
        e.preventDefault();
        const delta = e.deltaY > 0 ? -10 : 10;
        setZoom(z => Math.max(50, Math.min(200, z + delta)));
      } else if (e.shiftKey) {
        e.preventDefault();
        setContentWidth(w => Math.max(400, Math.min(1200, w - e.deltaY)));
      }
    };
    document.addEventListener('wheel', handleWheel, { passive: false });
    return () => document.removeEventListener('wheel', handleWheel);
  }, []);

  useEffect(() => {
    let isDraggingWindow = false;
    const handleMouseDown = (e: MouseEvent) => {
      if (e.buttons === 3 && focusMode) {
        e.preventDefault();
        isDraggingWindow = true;
        appWindow?.startDragging();
      }
    };
    const handleMouseUp = () => { isDraggingWindow = false; };
    document.addEventListener('mousedown', handleMouseDown);
    document.addEventListener('mouseup', handleMouseUp);
    return () => {
      document.removeEventListener('mousedown', handleMouseDown);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [appWindow, focusMode]);

  // Kinetic scroll: right-click + drag with iOS-style elastic overscroll
  // Applied to both content area and sidebar
  useEffect(() => {
    let cancelled = false;
    const cleanups: (() => void)[] = [];

    requestAnimationFrame(() => { requestAnimationFrame(() => {
      if (cancelled) return;

      // Content area
      const contentWrapper = document.querySelector('.content-area-scroll-wrapper');
      if (contentWrapper) {
        const viewport = contentWrapper.querySelector('[data-overlayscrollbars-viewport]') as HTMLElement;
        const contentDiv = contentWrapper.querySelector('.content-area-scroll') as HTMLElement;
        if (viewport && contentDiv) {
          cleanups.push(setupKineticScroll(viewport, contentDiv, kineticDragActiveRef));
        }
      }

      // Sidebar
      const sidebarWrapper = document.querySelector('.sidebar-scroll-wrapper');
      if (sidebarWrapper) {
        const viewport = sidebarWrapper.querySelector('[data-overlayscrollbars-viewport]') as HTMLElement;
        const sidebarDiv = sidebarWrapper.querySelector('.sidebar') as HTMLElement;
        if (viewport && sidebarDiv) {
          cleanups.push(setupKineticScroll(viewport, sidebarDiv, kineticDragActiveRef));
        }
      }
    }); });

    return () => {
      cancelled = true;
      cleanups.forEach(fn => fn());
    };
  }, [activeTabId, showSidebar]);

  const startSidebarResize = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setResizeStartState({ startX: e.clientX, startWidth: sidebarWidth });
  };

  useEffect(() => {
    if (!resizeStartState) return;
    const handleMouseMove = (e: MouseEvent) => {
      const zoomFactor = uiZoom / 100;
      const diff = (e.clientX - resizeStartState.startX) / zoomFactor;
      const newWidth = Math.max(150, Math.min(400, resizeStartState.startWidth + diff));
      setSidebarWidth(newWidth);
    };
    const handleMouseUp = () => {
      setResizeStartState(null);
    };
    document.body.style.cursor = 'col-resize';
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
    return () => {
      document.body.style.cursor = '';
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [resizeStartState, uiZoom]);

  // Persist sidebar width to localStorage
  useEffect(() => {
    localStorage.setItem('vesper-sidebar-width', String(Math.round(sidebarWidth)));
  }, [sidebarWidth]);

  // Persist UI zoom to localStorage
  useEffect(() => {
    localStorage.setItem('vesper-ui-zoom', String(Math.round(uiZoom)));
  }, [uiZoom]);

  // Menu bar keyboard navigation (WAI-ARIA menu pattern)
  const handleMenuBarKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'ArrowRight') {
      e.preventDefault();
      const next = (menuFocusIndex + 1) % menuItems.length;
      setMenuFocusIndex(next);
      menuItemRefs.current[menuItems[next]]?.focus();
      if (menuOpen) setMenuOpen(menuItems[next]);
    } else if (e.key === 'ArrowLeft') {
      e.preventDefault();
      const prev = (menuFocusIndex - 1 + menuItems.length) % menuItems.length;
      setMenuFocusIndex(prev);
      menuItemRefs.current[menuItems[prev]]?.focus();
      if (menuOpen) setMenuOpen(menuItems[prev]);
    } else if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      setMenuOpen(menuItems[menuFocusIndex]);
    } else if (e.key === 'Escape') {
      setMenuOpen(null);
    }
  };

  const handleMenuDropdownKeyDown = (e: React.KeyboardEvent) => {
    const menu = e.currentTarget as HTMLElement;
    const items = menu.querySelectorAll<HTMLElement>('[role="menuitem"]');
    if (!items.length) return;

    const currentIndex = Array.from(items).indexOf(document.activeElement as HTMLElement);

    if (e.key === 'ArrowDown') {
      e.preventDefault();
      const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
      items[next]?.focus();
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
      items[prev]?.focus();
    } else if (e.key === 'Home') {
      e.preventDefault();
      items[0]?.focus();
    } else if (e.key === 'End') {
      e.preventDefault();
      items[items.length - 1]?.focus();
    } else if (e.key === 'Escape') {
      e.preventDefault();
      setMenuOpen(null);
      menuItemRefs.current[menuItems[menuFocusIndex]]?.focus();
    } else if (e.key === 'ArrowRight') {
      e.preventDefault();
      const next = (menuFocusIndex + 1) % menuItems.length;
      setMenuFocusIndex(next);
      setMenuOpen(menuItems[next]);
    } else if (e.key === 'ArrowLeft') {
      e.preventDefault();
      const prev = (menuFocusIndex - 1 + menuItems.length) % menuItems.length;
      setMenuFocusIndex(prev);
      setMenuOpen(menuItems[prev]);
    }
  };

  // Tab bar keyboard navigation (WAI-ARIA tabs pattern with roving tabindex)
  const handleTabListKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'ArrowRight') {
      e.preventDefault();
      const next = (tabFocusIndex + 1) % tabs.length;
      setTabFocusIndex(next);
      tabRefs.current[next]?.focus();
    } else if (e.key === 'ArrowLeft') {
      e.preventDefault();
      const prev = (tabFocusIndex - 1 + tabs.length) % tabs.length;
      setTabFocusIndex(prev);
      tabRefs.current[prev]?.focus();
    } else if (e.key === 'Home') {
      e.preventDefault();
      setTabFocusIndex(0);
      tabRefs.current[0]?.focus();
    } else if (e.key === 'End') {
      e.preventDefault();
      const last = tabs.length - 1;
      setTabFocusIndex(last);
      tabRefs.current[last]?.focus();
    } else if (e.key === 'Delete') {
      e.preventDefault();
      if (tabs[tabFocusIndex]) closeTab(tabs[tabFocusIndex].id);
    } else if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      const tab = tabs[tabFocusIndex];
      if (tab) {
        setActiveTabId(tab.id);
        parseHeadings(tab.content);
      }
    }
  };

  // Auto-focus first menuitem when a dropdown opens
  useEffect(() => {
    if (menuOpen) {
      requestAnimationFrame(() => {
        const dropdown = document.querySelector('.menu-dropdown[role="menu"]');
        const firstItem = dropdown?.querySelector<HTMLElement>('[role="menuitem"]');
        firstItem?.focus();
      });
    }
  }, [menuOpen]);

  // Clamp tabFocusIndex when tabs are closed
  useEffect(() => {
    if (tabFocusIndex >= tabs.length && tabs.length > 0) {
      setTabFocusIndex(tabs.length - 1);
    }
  }, [tabs.length, tabFocusIndex]);

  // Scroll the focused tab into view when tabFocusIndex changes
  useEffect(() => {
    if (tabs.length > 1) {
      scrollToTabIndex(tabFocusIndex);
    }
  }, [tabFocusIndex, tabs.length, scrollToTabIndex]);

  const osScrollbarOptions = {
    scrollbars: { autoHide: 'scroll' as const, autoHideDelay: 800, theme: 'os-theme-dark' as const }
  };

  return (
    <div className={`app-container ${focusMode ? 'focus-mode' : ''}`} style={{ zoom: `${uiZoom}%` }}>
      <a href="#main-content" className="skip-link">Skip to content</a>
      <AnimatePresence>
        {isDraggingOver && (
          <motion.div className="drop-zone" role="region" aria-label="Drop zone for markdown files" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15, ease: materialEase }}>
            <motion.div className="drop-zone-content" initial={{ opacity: 0, scale: 0.92, y: 8 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.92, y: 8 }} transition={{ duration: 0.2, ease: materialEase }}>
              <motion.div className="drop-zone-icon-wrapper" animate={{ y: [0, -6, 0] }} transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}>
                <FileDown size={40} strokeWidth={1.5} />
              </motion.div>
              <div className="drop-zone-text">Drop markdown file here</div>
              <div className="drop-zone-hint">.md, .markdown, or .txt</div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence initial={false}>
        {!focusMode && (
          <motion.header key="title-bar" className="title-bar" initial={{ height: 0, opacity: 0 }} animate={{ height: 32, opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={smoothTransition} style={{ overflow: 'hidden' }}>
            <div className="title-bar-left">
              <div className="title-bar-icon"><FileText size={14} /></div>
              <span className="title-bar-text">Vesper</span>
            </div>
            <div className="title-bar-drag"></div>
            <div className="title-bar-controls">
              <button className="title-bar-button" onClick={() => appWindow?.minimize()} aria-label="Minimize"><Minus size={14} /></button>
              <button className="title-bar-button" onClick={() => appWindow?.toggleMaximize()} aria-label="Maximize"><Square size={12} /></button>
              <button className="title-bar-button close" onClick={closeWindow} aria-label="Close"><X size={14} /></button>
            </div>
          </motion.header>
        )}
      </AnimatePresence>

      <AnimatePresence initial={false}>
        {!focusMode && (
          <motion.div key="menu-bar" className="menu-bar" role="menubar" onKeyDown={handleMenuBarKeyDown} initial={{ height: 0, opacity: 0 }} animate={{ height: 28, opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={smoothTransition} style={{ overflow: 'hidden' }}>
            <button className="menu-item" role="menuitem" aria-haspopup="true" aria-expanded={menuOpen === 'file'} tabIndex={menuFocusIndex === 0 ? 0 : -1} ref={(el) => { menuItemRefs.current['file'] = el; }} onClick={() => { setMenuFocusIndex(0); setMenuOpen(prev => prev === 'file' ? null : 'file'); }}>File</button>
            <button className="menu-item" role="menuitem" aria-haspopup="true" aria-expanded={menuOpen === 'view'} tabIndex={menuFocusIndex === 1 ? 0 : -1} ref={(el) => { menuItemRefs.current['view'] = el; }} onClick={() => { setMenuFocusIndex(1); setMenuOpen(prev => prev === 'view' ? null : 'view'); }}>View</button>
            <button className="menu-item" role="menuitem" aria-haspopup="true" aria-expanded={menuOpen === 'help'} tabIndex={menuFocusIndex === 2 ? 0 : -1} ref={(el) => { menuItemRefs.current['help'] = el; }} onClick={() => { setMenuFocusIndex(2); setMenuOpen(prev => prev === 'help' ? null : 'help'); }}>Help</button>
            <AnimatePresence>
              {menuOpen === 'file' && menuItemRefs.current['file'] && (
                <motion.div className="menu-dropdown" role="menu" onKeyDown={handleMenuDropdownKeyDown} initial={{ opacity: 0, y: -5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -5 }} transition={smoothTransition} style={{ position: 'fixed', left: menuItemRefs.current['file']!.getBoundingClientRect().left, top: menuItemRefs.current['file']!.getBoundingClientRect().bottom - 6 }}>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { handleOpenDialog(); setMenuOpen(null); }}>Open File <span className="menu-shortcut">Ctrl+O</span></button>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { if (activeTabId) closeTab(activeTabId); setMenuOpen(null); }}>Close Tab <span className="menu-shortcut">Ctrl+W</span></button>
                  <div className="menu-separator"></div>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { closeWindow(); setMenuOpen(null); }}>Exit <span className="menu-shortcut">Ctrl+Q</span></button>
                </motion.div>
              )}
              {menuOpen === 'view' && menuItemRefs.current['view'] && (
                <motion.div className="menu-dropdown" role="menu" onKeyDown={handleMenuDropdownKeyDown} initial={{ opacity: 0, y: -5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -5 }} transition={smoothTransition} style={{ position: 'fixed', left: menuItemRefs.current['view']!.getBoundingClientRect().left, top: menuItemRefs.current['view']!.getBoundingClientRect().bottom - 6 }}>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { setShowSearch(s => !s); setMenuOpen(null); }}>Toggle Search <span className="menu-shortcut">Ctrl+F</span></button>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { setShowSidebar(s => !s); setMenuOpen(null); }}>Toggle Sidebar <span className="menu-shortcut">Ctrl+Shift+S</span></button>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { setFocusMode(f => !f); setMenuOpen(null); }}>Focus Mode <span className="menu-shortcut">F11</span></button>
                  <div className="menu-separator"></div>
                  <div className="menu-dropdown-zoom" onClick={e => e.stopPropagation()}>
                    <span>UI Scale</span>
                    <div className="zoom-spinner" aria-label="UI Scale">
                      <button className="zoom-spinner-btn" aria-label="Decrease UI scale" onClick={() => setUiZoom(z => Math.max(50, z - 10))}><Minus size={12} /></button>
                      <span className="zoom-spinner-value">{uiZoom}%</span>
                      <button className="zoom-spinner-btn" aria-label="Increase UI scale" onClick={() => setUiZoom(z => Math.min(200, z + 10))}><Plus size={12} /></button>
                    </div>
                  </div>
                </motion.div>
              )}
              {menuOpen === 'help' && menuItemRefs.current['help'] && (
                <motion.div className="menu-dropdown" role="menu" onKeyDown={handleMenuDropdownKeyDown} initial={{ opacity: 0, y: -5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -5 }} transition={smoothTransition} style={{ position: 'fixed', left: menuItemRefs.current['help']!.getBoundingClientRect().left, top: menuItemRefs.current['help']!.getBoundingClientRect().bottom - 6 }}>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { setShowShortcutsModal(true); setMenuOpen(null); }}>Keyboard Shortcuts</button>
                  <button className="menu-dropdown-item" role="menuitem" onClick={() => { setShowAboutModal(true); setMenuOpen(null); }}>About Vesper</button>
                </motion.div>
              )}
            </AnimatePresence>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {tabs.length > 1 && (
          <motion.div className="tab-bar" initial={{ height: 0, opacity: 0 }} animate={{ height: 38, opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.25, ease: materialEase }}>
            <button className={`tab-scroll-arrow ${tabScrollState.canLeft ? 'visible' : ''}`} aria-label="Scroll tabs left" onMouseDown={() => startScrollingTabs('left')} onMouseUp={stopScrollingTabs} onMouseLeave={stopScrollingTabs}><ChevronLeft size={14} /></button>
            <div className="tab-scroll-container" ref={tabScrollRef} role="tablist" onKeyDown={handleTabListKeyDown}>
              <AnimatePresence initial={false}>
                {tabs.map((tab, index) => (
                  <motion.div layout key={tab.id} id={`tab-${tab.id}`} role="tab" aria-selected={tab.id === activeTabId} tabIndex={index === tabFocusIndex ? 0 : -1} ref={(el) => { tabRefs.current[index] = el; }} className={`tab ${tab.id === activeTabId ? 'active' : ''}`} initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.9 }} transition={smoothTransition} onClick={() => { setTabFocusIndex(index); setActiveTabId(tab.id); parseHeadings(tab.content); }}>
                    <span className="tab-title">{tab.title}</span>
                    <button className="tab-close" aria-label="Close tab" onClick={(e) => { e.stopPropagation(); closeTab(tab.id); }}><X size={12} /></button>
                  </motion.div>
                ))}
              </AnimatePresence>
            </div>
            <button className={`tab-scroll-arrow ${tabScrollState.canRight ? 'visible' : ''}`} aria-label="Scroll tabs right" onMouseDown={() => startScrollingTabs('right')} onMouseUp={stopScrollingTabs} onMouseLeave={stopScrollingTabs}><ChevronRight size={14} /></button>
          </motion.div>
        )}
      </AnimatePresence>

      <div className="main-content">
        <AnimatePresence initial={false}>
          {showSidebar && (
            <motion.nav className="sidebar-scroll-wrapper" aria-label="Table of Contents" initial={{ width: 0, opacity: 0 }} animate={{ width: sidebarWidth, opacity: 1 }} exit={{ width: 0, opacity: 0 }} transition={resizeStartState ? { duration: 0 } : smoothTransition} style={{ height: '100%' }}>
              <OverlayScrollbarsComponent options={osScrollbarOptions} style={{ height: '100%' }}>
                <div className="sidebar" ref={sidebarRef}>
                  <h2 className="sidebar-heading">Contents</h2>
                  {headings.length > 0
                    ? headings.map(h => <button key={h.id} className={`sidebar-item h${h.level}`} onClick={() => scrollToHeading(h.text)}>{h.text}</button>)
                    : <div className="sidebar-empty">No headings</div>
                  }
                </div>
              </OverlayScrollbarsComponent>
              <div className="sidebar-resize-handle" onMouseDown={startSidebarResize}></div>
            </motion.nav>
          )}
        </AnimatePresence>

        <main id="main-content" className="content-column">
          <AnimatePresence>
            {showSearch && activeTab && (
              <motion.search className="search-bar" initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} transition={smoothTransition}>
                <input ref={searchInputRef} type="text" className="search-input" placeholder="Search..." value={searchQuery} onChange={e => { setSearchQuery(e.target.value); }} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setCurrentMatchIndex(i => (i + 1) % Math.max(searchMatches.length, 1)); setSearchTriggerCount(c => c + 1); } }} aria-label="Search document" autoFocus />
                {searchMatches.length > 0 && (
                  <>
                    <button className="search-nav-btn" aria-label="Previous match" onClick={() => { setCurrentMatchIndex(i => (i - 1 + searchMatches.length) % searchMatches.length); setSearchTriggerCount(c => c + 1); }}><ChevronLeft size={16} /></button>
                    <span className="search-results" role="status" aria-live="polite">{currentMatchIndex + 1} of {searchMatches.length}</span>
                    <button className="search-nav-btn" aria-label="Next match" onClick={() => { setCurrentMatchIndex(i => (i + 1) % searchMatches.length); setSearchTriggerCount(c => c + 1); }}><ChevronRight size={16} /></button>
                  </>
                )}
                {searchQuery && searchMatches.length === 0 && <span className="search-results" role="status" aria-live="polite">No results</span>}
              </motion.search>
            )}
          </AnimatePresence>

          <motion.div className="content-area-scroll-wrapper" role="tabpanel" aria-labelledby={activeTabId ? `tab-${activeTabId}` : undefined} key={activeTabId} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={smoothTransition}>
            <OverlayScrollbarsComponent options={osScrollbarOptions} style={{ height: '100%' }}>
              <div className="content-area-scroll" ref={contentRef} style={{ zoom: `${(zoom / uiZoom) * 100}%`, ...(!activeTab ? { height: '100%' } : {}) }}>
                {activeTab ? (
                  <article className="markdown-content" style={{ maxWidth: contentWidth }} dangerouslySetInnerHTML={{ __html: highlightedHtml }} />
                ) : (
                  <motion.div className="welcome-screen" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} transition={smoothTransition}>
                    <motion.div className="welcome-icon" animate={{ y: [0, -5, 0] }} transition={{ duration: 2, repeat: Infinity }}><FolderOpen size={64} /></motion.div>
                    <h1 className="welcome-title">Vesper</h1>
                    <p className="welcome-subtitle">A beautiful markdown reader</p>
                    <button className="welcome-button" onClick={handleOpenDialog}>Open File (Ctrl+O)</button>
                    <p className="welcome-hint">Or drag and drop a markdown file here</p>
                  </motion.div>
                )}
              </div>
            </OverlayScrollbarsComponent>
          </motion.div>
        </main>
      </div>

      <AnimatePresence>
        {showShortcutsModal && (
          <motion.div className="modal-overlay" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={smoothTransition} onClick={() => setShowShortcutsModal(false)}>
            <motion.div className="shortcuts-dialog" role="dialog" aria-modal="true" aria-labelledby="shortcuts-title" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={smoothTransition} onClick={e => e.stopPropagation()}>
              <div className="shortcuts-dialog-header">
                <h2 className="shortcuts-dialog-title" id="shortcuts-title">Keyboard Shortcuts</h2>
                <button className="shortcuts-dialog-close" aria-label="Close dialog" onClick={() => setShowShortcutsModal(false)}><X size={16} /></button>
              </div>
              <div className="shortcuts-dialog-body">
                <div>
                  <h3 className="shortcuts-dialog-group-title">File</h3>
                  <div className="shortcuts-dialog-item"><span>Open File</span><kbd>Ctrl+O</kbd></div>
                  <div className="shortcuts-dialog-item"><span>Close Tab</span><kbd>Ctrl+W</kbd></div>
                  <div className="shortcuts-dialog-item"><span>Exit</span><kbd>Ctrl+Q</kbd></div>
                </div>
                <div>
                  <h3 className="shortcuts-dialog-group-title">View</h3>
                  <div className="shortcuts-dialog-item"><span>Toggle Search</span><kbd>Ctrl+F</kbd></div>
                  <div className="shortcuts-dialog-item"><span>Toggle Sidebar</span><kbd>Ctrl+Shift+S</kbd></div>
                  <div className="shortcuts-dialog-item"><span>Focus Mode</span><kbd>F11</kbd></div>
                </div>
                <div className="shortcuts-dialog-nav-grid">
                  <h3 className="shortcuts-dialog-group-title">Navigation</h3>
                  <div className="shortcuts-dialog-items-row">
                    <div className="shortcuts-dialog-item"><span>Close Search / Sidebar</span><kbd>Esc</kbd></div>
                    <div className="shortcuts-dialog-item"><span>Zoom In / Out</span><kbd>Ctrl+Scroll</kbd></div>
                    <div className="shortcuts-dialog-item"><span>Content Width</span><kbd>Shift+Scroll</kbd></div>
                  </div>
                </div>
              </div>
            </motion.div>
          </motion.div>
        )}
        {showAboutModal && (
          <motion.div className="modal-overlay" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={smoothTransition} onClick={() => setShowAboutModal(false)}>
            <motion.div className="modal" role="dialog" aria-modal="true" aria-labelledby="about-title" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={smoothTransition} onClick={e => e.stopPropagation()}>
              <div className="modal-header">
                <h2 className="modal-title" id="about-title">About Vesper</h2>
                <button className="modal-close" aria-label="Close dialog" onClick={() => setShowAboutModal(false)}><X size={16} /></button>
              </div>
              <div className="modal-content about-content">
                <div className="about-icon"><FileText size={48} /></div>
                <h3 className="about-name">Vesper</h3>
                <p className="about-version">Version 1.0.0</p>
                <p className="about-description">A beautiful markdown reader</p>
                <p className="about-copyright">Built with Tauri + React</p>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {menuOpen && (
          <div className="menu-backdrop" aria-hidden="true" onClick={() => setMenuOpen(null)} />
        )}
      </AnimatePresence>

      {selectionRects.length > 0 && (
        <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 999 }}>
          {selectionRects.map((r, i) => (
            <div key={i} style={{ position: 'absolute', left: r.left, top: r.top, width: r.width, height: r.height, backgroundColor: 'rgba(56, 130, 221, 0.3)', borderRadius: 2 }} />
          ))}
        </div>
      )}

      <AnimatePresence>
        {contextMenu && (
          <motion.div ref={contextMenuRef} className="context-menu" role="menu" style={{ left: contextMenu.x, top: contextMenu.y }} initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={smoothTransition}>
            {contextMenu.selectedText && (
              <>
                <button className="context-menu-item" role="menuitem" onClick={() => { navigator.clipboard.writeText(contextMenu.selectedText); setContextMenu(null); }}>Copy</button>
                <div className="context-menu-separator"></div>
              </>
            )}
            <button className="context-menu-item" role="menuitem" onClick={() => { handleOpenDialog(); setContextMenu(null); }}>Open File</button>
            <div className="context-menu-separator"></div>
            <button className="context-menu-item" role="menuitem" onClick={() => { setShowSearch(s => !s); setContextMenu(null); }}>Toggle Search</button>
            <button className="context-menu-item" role="menuitem" onClick={() => { setShowSidebar(s => !s); setContextMenu(null); }}>Toggle Sidebar</button>
            <button className="context-menu-item" role="menuitem" onClick={() => { setFocusMode(f => !f); setContextMenu(null); }}>Focus Mode</button>
            <div className="context-menu-separator"></div>
            <button className="context-menu-item" role="menuitem" onClick={() => { setShowShortcutsModal(true); setContextMenu(null); }}>Keyboard Shortcuts</button>
            <button className="context-menu-item" role="menuitem" onClick={() => { setShowAboutModal(true); setContextMenu(null); }}>About</button>
            <div className="context-menu-separator"></div>
            <button className="context-menu-item" role="menuitem" onClick={() => { closeWindow(); setContextMenu(null); }}>Exit <span className="menu-shortcut">Ctrl+Q</span></button>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

export default App;
