import React from 'react';
import { createPortal } from 'react-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { Swiper as SwiperRoot, SwiperSlide } from 'swiper/react';
import type { Swiper as SwiperInstance } from 'swiper';
import { EffectCoverflow, Navigation, Pagination, A11y } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/effect-coverflow';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import { api } from '../services/api';

type Stage = 'task' | 'flow' | 'preview' | 'editor';

type Version = {
  id: string;
  taskId: string;
  originalAuthor: string;
  revisedBy?: string;
  versionNumber: number; // 1-based
  content: string; // translated text
  parentVersionId?: string; // lineage
};

type AnnotationCategory =
  | 'distortion'
  | 'omission'
  | 'register'
  | 'unidiomatic'
  | 'grammar'
  | 'spelling'
  | 'punctuation'
  | 'addition'
  | 'other';

type Annotation = {
  id: string;
  versionId: string;
  start: number; // inclusive, char index in version.content
  end: number;   // exclusive
  category: AnnotationCategory;
  comment?: string;
  correction?: string;
  createdBy?: string;
  createdAt: number;
  updatedAt: number;
};

type Task = {
  id: string;
  title: string;
  sourceText: string;
  createdBy?: string;
};

// ---- Filename helpers ----
function toSafeName(input: string): string {
  // Keep CJK and most unicode letters; remove only forbidden filename characters and collapse whitespace
  return String(input || '')
    .replace(/[\\/:*?"<>|]+/g, '_') // forbidden on Windows/macOS
    .replace(/[\u0000-\u001F\u007F]/g, '') // control chars
    .trim()
    .replace(/\s+/g, '_');
}
function pad2(n: number): string {
  return n < 10 ? `0${n}` : String(n);
}
function yyyymmdd_hhmm(d = new Date()): string {
  const Y = d.getFullYear();
  const M = pad2(d.getMonth() + 1);
  const D = pad2(d.getDate());
  const h = pad2(d.getHours());
  const m = pad2(d.getMinutes());
  return `${Y}${M}${D}_${h}${m}`;
}

// Download helpers with fail-safes
async function saveBlobToDisk(blob: Blob, filename: string) {
  const url = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
  window.URL.revokeObjectURL(url);
}
function saveTextFallback(text: string, filename: string) {
  const blob = new Blob([text || ''], { type: 'text/plain;charset=utf-8' });
  const fallbackName = filename.replace(/\.docx$/i, '') + '.txt';
  return saveBlobToDisk(blob, fallbackName);
}
async function tryFetchDocx(url: string, body: any): Promise<Response> {
  return await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
}

const mockTasks: Task[] = [
  { id: 't1', title: 'Refinity Demo Task 1', sourceText: 'The quick brown fox jumps over the lazy dog.' },
  { id: 't2', title: 'Refinity Demo Task 2', sourceText: 'To be, or not to be, that is the question.' },
];

const Refinity: React.FC = () => {
  // Check if we're in tutorial mode - use state to make it reactive
  const [isTutorialMode, setIsTutorialMode] = React.useState<boolean>(() => {
    try {
      return localStorage.getItem('refinityMode') === 'tutorial';
    } catch {
      return false;
    }
  });

  // Listen for changes to tutorial mode
  React.useEffect(() => {
    const checkTutorialMode = () => {
      try {
        setIsTutorialMode(localStorage.getItem('refinityMode') === 'tutorial');
      } catch {
        setIsTutorialMode(false);
      }
    };
    // Check immediately
    checkTutorialMode();
    // Listen for storage events (when TutorialRefinity sets it)
    window.addEventListener('storage', checkTutorialMode);
    // Also poll periodically to catch changes from same window
    const interval = setInterval(checkTutorialMode, 100);
    return () => {
      window.removeEventListener('storage', checkTutorialMode);
      clearInterval(interval);
    };
  }, []);

  const getApiBase = React.useCallback((endpoint: string) => {
    // Use localhost when running locally, otherwise use api base
    const isLocalhost = typeof window !== 'undefined' && /^(localhost|127\.0\.0\.1)$/i.test(window.location.hostname);
    const base = isLocalhost ? 'http://localhost:5000' : (((api.defaults as any)?.baseURL as string || '').replace(/\/$/, ''));
    // Check localStorage directly to ensure we always have the latest value
    try {
      const mode = localStorage.getItem('refinityMode');
      if (mode === 'tutorial') {
        return `${base}/api/tutorial-refinity${endpoint}`;
      }
    } catch {}
    return `${base}/api/refinity${endpoint}`;
  }, []);

  const [stage, setStage] = React.useState<Stage>(() => {
    try {
      const h = String(window.location.hash || '');
      const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
      const params = new URLSearchParams(q);
      const s = String(params.get('stage') || '');
      if (s === 'flow' || s === 'preview' || s === 'editor' || s === 'task') return s as any;
    } catch {}
    return 'task';
  });
  const [tasks, setTasks] = React.useState<Task[]>([]);
  const [selectedTaskId, setSelectedTaskId] = React.useState<string>(() => {
    try {
      // Check if in tutorial mode and restore from localStorage
      const isTutorial = localStorage.getItem('refinityMode') === 'tutorial';
      if (isTutorial) {
        const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
        if (weekNumber > 0) {
          const saved = localStorage.getItem(`tutorial_selected_task_week_${weekNumber}`);
          if (saved) return saved;
        }
      }
      // Otherwise, try URL hash
      const h = String(window.location.hash || '');
      const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
      const params = new URLSearchParams(q);
      return String(params.get('taskId') || '');
    } catch { return ''; }
  });
  // Track user-initiated task selections to prevent effect from overriding them
  const userSelectedTaskIdRef = React.useRef<string | null>(null);
  const [versions, setVersions] = React.useState<Version[]>([]);
  const [currentVersionId, setCurrentVersionId] = React.useState<string | null>(() => {
    try {
      const h = String(window.location.hash || '');
      const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
      const params = new URLSearchParams(q);
      return String(params.get('versionId') || '') || null;
    } catch { return null; }
  });
  const [previewVersionId, setPreviewVersionId] = React.useState<string | null>(() => {
    try {
      const h = String(window.location.hash || '');
      const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
      const params = new URLSearchParams(q);
      return String(params.get('previewId') || '') || null;
    } catch { return null; }
  });
  const [isFullscreen, setIsFullscreen] = React.useState<boolean>(() => {
    try {
      const h = String(window.location.hash || '');
      const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
      const params = new URLSearchParams(q);
      return params.get('fullscreen') === '1';
    } catch { return false; }
  });
  const [compareUIOpen, setCompareUIOpen] = React.useState<boolean>(false);
  const [compareA, setCompareA] = React.useState<string>('');
  const [compareB, setCompareB] = React.useState<string>('');
  const [compareModalOpen, setCompareModalOpen] = React.useState<boolean>(false);
  const [lastDiffIds, setLastDiffIds] = React.useState<{ a?: string; b?: string }>({});
  const [compareDiffHtml, setCompareDiffHtml] = React.useState<string>('');
  const [compareLoading, setCompareLoading] = React.useState<boolean>(false);
  const [compareDownloadOpen, setCompareDownloadOpen] = React.useState<boolean>(false);
  const [compareMenuPos, setCompareMenuPos] = React.useState<{ left: number; top: number } | null>(null);
  const compareBtnRef = React.useRef<HTMLButtonElement | null>(null);
  const [revDownloadOpen, setRevDownloadOpen] = React.useState<boolean>(false);
  // Chrome-only UA flag for layout tweaks that should not affect Safari/Firefox/Edge
  React.useEffect(() => {
    try {
      const ua = navigator.userAgent || '';
      const isChrome = /Chrome\//.test(ua) && !/Edg\//.test(ua) && !/OPR\//.test(ua);
      const root = document.documentElement;
      if (isChrome) root.classList.add('is-chrome');
      else root.classList.remove('is-chrome');
    } catch {}
  }, []);
  // --- Route persistence (hash-based) ---
  const restoringRef = React.useRef<boolean>(false);
  const appliedInitialRouteRef = React.useRef<boolean>(false);
  const initialRouteRef = React.useRef<{ stage?: Stage; taskId?: string; versionId?: string; previewId?: string; fullscreen?: boolean } | null>(null);
  const parseRouteHash = React.useCallback((): Record<string, string> => {
    try {
      const h = String(window.location.hash || '');
      const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
      const out: Record<string, string> = {};
      q.split('&').forEach(p => {
        const [k, v] = p.split('=');
        if (!k) return;
        out[decodeURIComponent(k)] = decodeURIComponent(v || '');
      });
      return out;
    } catch { return {}; }
  }, []);
  const writeRouteHash = React.useCallback((s: Partial<{ stage: Stage; taskId: string; versionId: string; previewId: string; fullscreen: string }>) => {
    try {
      if (restoringRef.current) return;
      const params = new URLSearchParams();
      const current = parseRouteHash();
      const merged: Record<string, string> = { ...current, ...Object.fromEntries(Object.entries(s).filter(([,v]) => v !== undefined && v !== null).map(([k,v]) => [k, String(v)])) };
      Object.entries(merged).forEach(([k, v]) => { if (v) params.set(k, v); });
      const next = `#/dr?${params.toString()}`;
      if (String(window.location.hash) !== next) {
        window.location.hash = next;
      }
    } catch {}
  }, [parseRouteHash]);
  // Annotations (Version Flow)
  const [showAnnotations, setShowAnnotations] = React.useState<boolean>(true);
  const [annotationPopover, setAnnotationPopover] = React.useState<{ left: number; top: number; versionId: string; range: { start: number; end: number } } | null>(null);
  const [annotationModalOpen, setAnnotationModalOpen] = React.useState<boolean>(false);
  const [editingAnnotation, setEditingAnnotation] = React.useState<Annotation | null>(null);
  const [modalCategory, setModalCategory] = React.useState<AnnotationCategory>('distortion');
  const [modalComment, setModalComment] = React.useState<string>('');
  const [username] = React.useState<string>(() => {
    try {
      const u = localStorage.getItem('user');
      const name = u ? (JSON.parse(u)?.name || JSON.parse(u)?.email || 'Anonymous') : 'Anonymous';
      return String(name);
    } catch {
      return 'Anonymous';
    }
  });
  const getIsAdmin = React.useCallback(() => {
    try {
      const u = localStorage.getItem('user');
      const role = u ? (JSON.parse(u)?.role || '') : '';
      const viewMode = (localStorage.getItem('viewMode') || 'auto').toString().toLowerCase();
      if (viewMode === 'student') return false;
      return String(role).toLowerCase() === 'admin';
    } catch { return false; }
  }, []);
  const [isAdmin, setIsAdmin] = React.useState<boolean>(() => getIsAdmin());
  React.useEffect(() => {
    const refresh = () => setIsAdmin(getIsAdmin());
    window.addEventListener('storage', refresh);
    const id = window.setInterval(refresh, 500);
    return () => { window.removeEventListener('storage', refresh); window.clearInterval(id); };
  }, [getIsAdmin]);

  // File upload (.docx placeholder)
  const [uploading, setUploading] = React.useState(false);
  const fileInputRef = React.useRef<HTMLInputElement | null>(null);

  // Add Task UI state
  const [showAddTask, setShowAddTask] = React.useState<boolean>(false);
  const [newTaskTitle, setNewTaskTitle] = React.useState<string>('');
  const [newTaskSource, setNewTaskSource] = React.useState<string>('');
  const addTaskFileRef = React.useRef<HTMLInputElement | null>(null);
  const [addingSourceUploading, setAddingSourceUploading] = React.useState<boolean>(false);
  const [taskMenuOpen, setTaskMenuOpen] = React.useState<boolean>(false);
  const [pastedTranslation, setPastedTranslation] = React.useState<string>('');
  const [taskStageNote, setTaskStageNote] = React.useState<string>('');
  const [showPreviewDiff, setShowPreviewDiff] = React.useState<boolean>(false);
  const [inputTab, setInputTab] = React.useState<'paste' | 'upload'>('paste');
  const [editingVersionId, setEditingVersionId] = React.useState<string | null>(null);
  const [editingTaskOpen, setEditingTaskOpen] = React.useState<boolean>(false);
  const [editTaskTitle, setEditTaskTitle] = React.useState<string>('');
  const [editTaskSource, setEditTaskSource] = React.useState<string>('');
  const [savingTaskEdit, setSavingTaskEdit] = React.useState<boolean>(false);

  const task = React.useMemo(() => tasks.find(t => t.id === selectedTaskId) || tasks[0], [tasks, selectedTaskId]);
  const taskVersions = React.useMemo(() => versions.filter(v => v.taskId === (task?.id || '')), [versions, task?.id]);
  // Load tasks
  // Simple session cache keys - use different keys for tutorial mode to avoid conflicts
  // Check localStorage directly for tutorial mode
  const getTutorialMode = React.useCallback(() => {
    try {
      return localStorage.getItem('refinityMode') === 'tutorial';
    } catch {
      return false;
    }
  }, []);
  
  const TASKS_CACHE_KEY = React.useMemo(() => {
    const isTut = getTutorialMode();
    return isTut ? 'tutorial_refinity_tasks_cache_v1' : 'refinity_tasks_cache_v1';
  }, [getTutorialMode]);
  
  const versionsCacheKey = React.useCallback((taskId: string) => {
    const isTut = getTutorialMode();
    return isTut ? `tutorial_refinity_versions_cache_${taskId}_v1` : `refinity_versions_cache_${taskId}_v1`;
  }, [getTutorialMode]);

  React.useEffect(() => {
    (async () => {
      try {
        // Capture initial route once on first load
        if (!initialRouteRef.current) {
          const params = parseRouteHash();
          const desiredStage = (params.stage as Stage) || undefined;
          const fullscreen = params.fullscreen === '1';
          initialRouteRef.current = { stage: desiredStage, taskId: params.taskId, versionId: params.versionId, previewId: params.previewId, fullscreen };
          if (fullscreen) setIsFullscreen(true);
        }
        // Hydrate from cache immediately
        try {
          const cached = JSON.parse(sessionStorage.getItem(TASKS_CACHE_KEY) || '[]');
          if (Array.isArray(cached) && cached.length && tasks.length === 0) {
            setTasks(cached);
            // Use a function to get current selectedTaskId to avoid stale closure
            setSelectedTaskId(prev => {
              if (prev) return prev; // Don't override if already set
              const initTaskId = initialRouteRef.current?.taskId;
              const isTutorial = getTutorialMode();
              if (initTaskId && cached.some((t:any)=>t.id===initTaskId)) {
                if (isTutorial) {
                  const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
                  if (weekNumber > 0) {
                    localStorage.setItem(`tutorial_selected_task_week_${weekNumber}`, initTaskId);
                  }
                }
                return initTaskId;
              } else if (cached.length) {
                if (isTutorial) {
                  const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
                  if (weekNumber > 0) {
                    const savedTaskId = localStorage.getItem(`tutorial_selected_task_week_${weekNumber}`);
                    if (savedTaskId && cached.some((t:any)=>t.id===savedTaskId)) {
                      return savedTaskId;
                    } else {
                      localStorage.setItem(`tutorial_selected_task_week_${weekNumber}`, cached[0].id);
                    }
                  }
                }
                return cached[0].id;
              }
              return prev;
            });
          }
        } catch {}
        // In tutorial mode, include weekNumber in query
        let tasksUrl = getApiBase('/tasks');
        if (getTutorialMode()) {
          const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
          if (weekNumber > 0) {
            tasksUrl += `?weekNumber=${weekNumber}`;
          }
        }
        // Send user identification headers for proper backend handling
        const user = JSON.parse(localStorage.getItem('user') || '{}');
        const headers: any = {};
        if (user.name) headers['x-user-name'] = user.name;
        if (user.email) headers['x-user-email'] = user.email;
        if (user.role === 'admin') {
          headers['x-user-role'] = 'admin';
          headers['user-role'] = 'admin';
        }
        const resp = await fetch(tasksUrl, { headers });
        const data: any[] = await resp.json().catch(()=>[]);
        // In tutorial mode, only show tasks created by admin
        let normalized: Task[] = Array.isArray(data) ? data.map(d => ({ id: d._id, title: d.title, sourceText: d.sourceText, createdBy: d.createdBy })) : [];
        const currentTutorialMode = getTutorialMode();
        if (currentTutorialMode) {
          // Filter to only show admin-created tasks
          const user = JSON.parse(localStorage.getItem('user') || '{}');
          const isAdmin = user.role === 'admin';
          if (!isAdmin) {
            // Non-admin users: only show tasks created by admin
            normalized = normalized.filter(t => {
              // Check if task was created by admin (createdBy should be admin user ID or email)
              // For now, we'll show all tasks and let backend filter
              return true;
            });
          }
        }
        setTasks(normalized);
        try { sessionStorage.setItem(TASKS_CACHE_KEY, JSON.stringify(normalized)); } catch {}
        // Apply initial route task selection if available
        // Use functional update to get current state and avoid overriding user selections
        setSelectedTaskId(prev => {
          // Don't override if user just selected a task
          const currentUserSelection = userSelectedTaskIdRef.current;
          if (currentUserSelection && normalized.some(t => t.id === currentUserSelection)) {
            return currentUserSelection;
          }
          // If current selection is valid, keep it
          if (prev && normalized.some(t => t.id === prev)) {
            return prev;
          }
          // Otherwise, set initial selection
          const initTaskId = initialRouteRef.current?.taskId;
          if (normalized.length) {
            if (initTaskId && normalized.some(t => t.id === initTaskId)) {
              if (currentTutorialMode) {
                const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
                if (weekNumber > 0) {
                  localStorage.setItem(`tutorial_selected_task_week_${weekNumber}`, initTaskId);
                }
              }
              return initTaskId;
            } else {
              // In tutorial mode, try to restore from localStorage
              if (currentTutorialMode) {
                const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
                if (weekNumber > 0) {
                  const savedTaskId = localStorage.getItem(`tutorial_selected_task_week_${weekNumber}`);
                  if (savedTaskId && normalized.some(t => t.id === savedTaskId)) {
                    return savedTaskId;
                  } else {
                    localStorage.setItem(`tutorial_selected_task_week_${weekNumber}`, normalized[0].id);
                  }
                }
              }
              return normalized[0].id;
            }
          }
          return prev;
        });
      } catch {}
    })();
  }, [getApiBase, TASKS_CACHE_KEY, tasks.length, getTutorialMode]);

  // Load versions when task changes
  React.useEffect(() => {
    (async () => {
      if (!task?.id) { setVersions([]); return; }
      try {
        // Hydrate versions from cache for instant UI
        try {
          const cached = JSON.parse(sessionStorage.getItem(versionsCacheKey(task.id)) || '[]');
          // In tutorial mode, filter to show admin-created first version + user's own versions (unless admin)
          let filteredCache = cached;
          const currentTutorialMode = getTutorialMode();
          if (currentTutorialMode && !isAdmin) {
            filteredCache = cached.filter((v: any) => {
              // Include version 1 (admin-created starting point) OR user's own versions
              return v.versionNumber === 1 || v.originalAuthor === username || v.revisedBy === username;
            });
          }
          if (Array.isArray(filteredCache) && filteredCache.length) {
            setVersions(filteredCache);
            // If versionId missing but stage requests editor, default to latest cached
            if (stage === 'editor' && !currentVersionId) {
              setCurrentVersionId(filteredCache[filteredCache.length - 1]?.id || null);
            }
          }
        } catch {}
        // Send user identification headers for backend filtering
        const user = JSON.parse(localStorage.getItem('user') || '{}');
        const headers: any = {};
        if (user.name) headers['x-user-name'] = user.name;
        if (user.email) headers['x-user-email'] = user.email;
        if (user.role === 'admin') {
          headers['x-user-role'] = 'admin';
          headers['user-role'] = 'admin';
        }
        const resp = await fetch(getApiBase(`/tasks/${encodeURIComponent(task.id)}/versions`), { headers });
        const data: any[] = await resp.json().catch(()=>[]);
        let normalized: Version[] = Array.isArray(data) ? data.map(d => ({ id: d._id, taskId: d.taskId, originalAuthor: d.originalAuthor, revisedBy: d.revisedBy, versionNumber: d.versionNumber, content: d.content, parentVersionId: d.parentVersionId })) : [];
        // In tutorial mode, filter to show admin-created first version + user's own versions (unless admin)
        const currentTutorialMode = getTutorialMode();
        if (currentTutorialMode && !isAdmin) {
          normalized = normalized.filter(v => {
            // Include version 1 (admin-created starting point) OR user's own versions
            return v.versionNumber === 1 || v.originalAuthor === username || v.revisedBy === username;
          });
        }
        setVersions(normalized);
        try { sessionStorage.setItem(versionsCacheKey(task.id), JSON.stringify(normalized)); } catch {}
        // Restore stage/version from initial route if present
        const init = initialRouteRef.current;
        if (!appliedInitialRouteRef.current && init?.stage && normalized.length && (!init.taskId || init.taskId === task.id)) {
          restoringRef.current = true;
          try {
            if (init.stage === 'editor' && init.versionId && normalized.some(v => v.id === init.versionId)) {
              setCurrentVersionId(init.versionId);
              setStage('editor');
            } else if (init.stage === 'preview' && init.previewId && normalized.some(v => v.id === init.previewId)) {
              setPreviewVersionId(init.previewId);
              setStage('preview');
            } else if (init.stage === 'editor') {
              // Stay in editor; choose latest if specific version missing
              setCurrentVersionId(normalized[normalized.length-1]?.id || null);
              setStage('editor');
            } else if (init.stage === 'preview') {
              setPreviewVersionId(normalized[normalized.length-1]?.id || null);
              setStage('preview');
            } else if (init.stage === 'flow') {
              setStage('flow');
            }
          } finally {
            // small defer to avoid immediate hash writes during restore
            setTimeout(() => { restoringRef.current = false; }, 50);
            appliedInitialRouteRef.current = true;
            initialRouteRef.current = null;
          }
        } else {
          // No init; do not force stage change if already 'editor' or 'preview'
          if (!currentVersionId) setCurrentVersionId(normalized.length ? normalized[normalized.length-1].id : null);
        }
      } catch { setVersions([]); }
    })();
  }, [task?.id, getApiBase, getTutorialMode, versionsCacheKey, isAdmin]);

  const deleteVersion = React.useCallback(async (versionId: string) => {
    try {
      const ok = window.confirm('Delete this version? This cannot be undone.');
      if (!ok) return;
      const resp = await fetch(getApiBase(`/versions/${encodeURIComponent(versionId)}`), {
        method: 'DELETE',
        headers: { 'x-user-name': username, ...(isAdmin ? { 'x-user-role': 'admin' } : {}) }
      });
      if (!resp.ok) throw new Error('Delete failed');
      setVersions(prev => prev.filter(v => v.id !== versionId));
    } catch {}
  }, [isAdmin, username]);

  const saveEditedVersion = React.useCallback(async (versionId: string, newContent: string) => {
    const resp = await fetch(getApiBase(`/versions/${encodeURIComponent(versionId)}`), {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json', 'x-user-name': username },
      body: JSON.stringify({ content: newContent })
    });
    const updated = await resp.json().catch(()=>({}));
    if (!resp.ok) throw new Error(updated?.error || 'Update failed');
    setVersions(prev => prev.map(v => v.id === versionId ? { ...v, content: updated.content } as any : v));
    setEditingVersionId(null);
    setStage('flow');
  }, [username]);

  const openEditTask = React.useCallback(() => {
    const t = tasks.find(t => t.id === selectedTaskId);
    if (!t) return;
    setEditTaskTitle(t.title || '');
    setEditTaskSource(t.sourceText || '');
    setEditingTaskOpen(true);
  }, [tasks, selectedTaskId]);

  const saveTaskEdit = React.useCallback(async () => {
    if (!selectedTaskId) return;
    setSavingTaskEdit(true);
    try {
      const token = localStorage.getItem('token');
      const user = JSON.parse(localStorage.getItem('user') || '{}');
      const viewMode = (localStorage.getItem('viewMode') || 'auto');
      const effectiveRole = viewMode === 'student' ? 'student' : (user.role || 'visitor');
      
      const headers: any = { 'Content-Type': 'application/json', 'x-user-name': username };
      if (token) {
        headers['Authorization'] = `Bearer ${token}`;
      }
      headers['x-user-role'] = effectiveRole;
      headers['user-role'] = effectiveRole;
      
      const resp = await fetch(getApiBase(`/tasks/${encodeURIComponent(selectedTaskId)}`), {
        method: 'PUT',
        headers,
        body: JSON.stringify({ title: editTaskTitle, sourceText: editTaskSource })
      });
      const updated = await resp.json().catch(()=>({}));
      if (!resp.ok) throw new Error(updated?.error || 'Update failed');
      setTasks(prev => prev.map(t => t.id === selectedTaskId ? { ...t, title: updated.title, sourceText: updated.sourceText } : t));
      setEditingTaskOpen(false);
    } catch (e) {
      alert('Failed to update task');
    } finally {
      setSavingTaskEdit(false);
    }
  }, [selectedTaskId, editTaskTitle, editTaskSource, username, getApiBase, isAdmin]);

  const [flowIndex, setFlowIndex] = React.useState<number>(0);
  // Keep URL hash in sync with primary navigation state
  React.useEffect(() => {
    writeRouteHash({
      stage,
      taskId: task?.id || '',
      versionId: currentVersionId || '',
      previewId: previewVersionId || '',
      fullscreen: isFullscreen ? '1' : '0'
    });
  }, [stage, task?.id, currentVersionId, previewVersionId, isFullscreen, writeRouteHash]);
  const focusedIndex = React.useMemo(() => {
    const idx = taskVersions.findIndex(v => v.id === currentVersionId);
    if (idx >= 0) return idx;
    return Math.min(Math.max(taskVersions.length - 1, 0), flowIndex);
  }, [taskVersions, currentVersionId, flowIndex]);
  const centerVersionId = React.useMemo(() => (taskVersions[flowIndex]?.id) || '', [taskVersions, flowIndex]);
  const currentVersion = React.useMemo(() => taskVersions.find(v => v.id === currentVersionId) || null, [taskVersions, currentVersionId]);
  const previewVersion = React.useMemo(() => taskVersions.find(v => v.id === previewVersionId) || null, [taskVersions, previewVersionId]);

  // Keyboard navigation disabled per request (use arrow buttons only)

  // Non-visual overlay to guarantee Revise clickability on all platforms without changing layout
  const reviseBtnRefs = React.useRef<Record<string, HTMLButtonElement | null>>({});
  const [reviseOverlay, setReviseOverlay] = React.useState<{ left: number; top: number; width: number; height: number; vid: string } | null>(null);
  const updateReviseOverlay = React.useCallback(() => {
    try {
      const v = taskVersions[flowIndex];
      if (!v) { setReviseOverlay(null); return; }
      const el = reviseBtnRefs.current[v.id || ''];
      if (!el) { setReviseOverlay(null); return; }
      const r = el.getBoundingClientRect();
      if (r.width === 0 || r.height === 0) { setReviseOverlay(null); return; }
      setReviseOverlay({ left: Math.max(0, r.left), top: Math.max(0, r.top), width: r.width, height: r.height, vid: v.id });
    } catch { setReviseOverlay(null); }
  }, [taskVersions, flowIndex]);
  React.useLayoutEffect(() => { updateReviseOverlay(); }, [updateReviseOverlay, isFullscreen, compareUIOpen, revDownloadOpen]);
  React.useEffect(() => {
    // After slide changes, measure across a few frames and after a short delay
    let raf1 = 0, raf2 = 0, raf3 = 0;
    const tick = () => updateReviseOverlay();
    raf1 = window.requestAnimationFrame(() => {
      tick();
      raf2 = window.requestAnimationFrame(() => {
        tick();
        raf3 = window.requestAnimationFrame(() => tick());
      });
    });
    const t = window.setTimeout(tick, 260);
    return () => {
      window.cancelAnimationFrame(raf1);
      window.cancelAnimationFrame(raf2);
      window.cancelAnimationFrame(raf3);
      window.clearTimeout(t);
    };
  }, [flowIndex, centerVersionId, updateReviseOverlay]);
  React.useEffect(() => {
    const onResize = () => updateReviseOverlay();
    const onScroll = () => updateReviseOverlay();
    window.addEventListener('resize', onResize, { passive: true } as any);
    window.addEventListener('scroll', onScroll, { passive: true } as any);
    return () => {
      window.removeEventListener('resize', onResize as any);
      window.removeEventListener('scroll', onScroll as any);
    };
  }, [updateReviseOverlay]);

  const uploadDocx = async (file: File) => {
    setUploading(true);
    try {
      const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
      const form = new FormData();
      form.append('file', file);
      // Use getApiBase to route to correct API (tutorial-refinity or refinity)
      const resp = await fetch(`${base}${getApiBase('/parse')}`, { method: 'POST', body: form });
      const data = await resp.json().catch(()=>({}));
      if (!resp.ok) throw new Error(data?.error || 'Failed to parse document');
      const baseText = String(data?.text || '').trim() || `Uploaded translation by ${username}`;
      // Persist as new version - use getApiBase to route to correct API
      const resp2 = await fetch(`${base}${getApiBase(`/tasks/${encodeURIComponent(task?.id || '')}/versions`)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ originalAuthor: username, revisedBy: undefined, content: baseText }) });
      const saved = await resp2.json().catch(()=>({}));
      if (!resp2.ok) throw new Error(saved?.error || 'Failed to save version');
      const newVersion: Version = { id: saved._id, taskId: saved.taskId, originalAuthor: saved.originalAuthor, revisedBy: saved.revisedBy, versionNumber: saved.versionNumber, content: saved.content, parentVersionId: saved.parentVersionId };
      setVersions(prev => [...prev, newVersion]);
      // Update cache
      try {
        const cacheKey = versionsCacheKey(task?.id || '');
        const cached = JSON.parse(sessionStorage.getItem(cacheKey) || '[]');
        const updated = [...cached, newVersion];
        sessionStorage.setItem(cacheKey, JSON.stringify(updated));
      } catch {}
      setCurrentVersionId(newVersion.id);
      setStage('flow');
    } catch (e) {
      console.error('Error uploading document:', e);
    } finally {
      setUploading(false);
    }
  };

  // ---------- Annotation helpers ----------
  const ANNO_STORE_KEY = 'refinity_annotations_v1';
  const loadAnnotations = React.useCallback((): Annotation[] => {
    try {
      const raw = localStorage.getItem(ANNO_STORE_KEY);
      const arr = raw ? JSON.parse(raw) : [];
      return Array.isArray(arr) ? arr : [];
    } catch { return []; }
  }, []);
  const saveAnnotations = React.useCallback((list: Annotation[]) => {
    try { localStorage.setItem(ANNO_STORE_KEY, JSON.stringify(list)); } catch {}
  }, []);
  const [annotations, setAnnotations] = React.useState<Annotation[]>(() => loadAnnotations());
  React.useEffect(() => { saveAnnotations(annotations); }, [annotations, saveAnnotations]);
  const annoForVersion = React.useCallback((versionId: string) => annotations.filter(a => a.versionId === versionId), [annotations]);
  const addAnnotation = React.useCallback((a: Annotation) => setAnnotations(prev => [...prev, a]), []);
  const updateAnnotation = React.useCallback((a: Annotation) => setAnnotations(prev => prev.map(x => x.id === a.id ? a : x)), []);
  const deleteAnnotationById = React.useCallback((id: string) => setAnnotations(prev => prev.filter(a => a.id !== id)), []);

  const CATEGORY_LABELS: Record<AnnotationCategory, string> = {
    distortion: 'Distortion',
    omission: 'Unjustified omission',
    register: 'Inappropriate register',
    unidiomatic: 'Unidiomatic expression',
    grammar: 'Error of grammar, syntax',
    spelling: 'Error of spelling',
    punctuation: 'Error of punctuation',
    addition: 'Unjustified addition',
    other: 'Other',
  };
  const CATEGORY_CLASS: Record<AnnotationCategory, string> = {
    distortion: 'bg-rose-100',
    omission: 'bg-amber-100',
    register: 'bg-indigo-100',
    unidiomatic: 'bg-purple-100',
    grammar: 'bg-blue-100',
    spelling: 'bg-green-100',
    punctuation: 'bg-teal-100',
    addition: 'bg-pink-100',
    other: 'bg-gray-100',
  };
  function escapeHtml(s: string): string {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }
  function renderAnnotatedHtml(text: string, versionId: string, enabled: boolean): string {
    if (!enabled) return escapeHtml(text);
    const list = annoForVersion(versionId).slice().sort((a,b)=>a.start-b.start);
    if (!list.length) return escapeHtml(text);
    let html = '';
    let pos = 0;
    for (const a of list) {
      const start = Math.max(0, Math.min(a.start, text.length));
      const end = Math.max(start, Math.min(a.end, text.length));
      if (pos < start) html += escapeHtml(text.slice(pos, start));
      const label = CATEGORY_LABELS[a.category] || 'Note';
      const cls = CATEGORY_CLASS[a.category] || 'bg-gray-100 ring-gray-200';
      const span = escapeHtml(text.slice(start, end)) || '&nbsp;';
      html += `<span data-anno-id="${a.id}" class="inline rounded-[6px] ring-1 ${cls} px-0.5" title="${label}${a.comment ? ': '+escapeHtml(a.comment) : ''}">${span}</span>`;
      pos = end;
    }
    if (pos < text.length) html += escapeHtml(text.slice(pos));
    return html;
  }
  function rangeToOffsets(container: HTMLElement, range: Range, fullText: string): { start: number; end: number } {
    const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);
    let start = -1;
    let end = -1;
    let charCount = 0;
    while (walker.nextNode()) {
      const node = walker.currentNode as Text;
      const text = node.nodeValue || '';
      if (node === range.startContainer) start = charCount + range.startOffset;
      if (node === range.endContainer) end = charCount + range.endOffset;
      charCount += text.length;
    }
    if (start < 0 || end < 0) {
      const sel = range.toString();
      const idx = fullText.indexOf(sel);
      if (idx >= 0) { start = idx; end = idx + sel.length; }
    }
    start = Math.max(0, Math.min(start, fullText.length));
    end = Math.max(start, Math.min(end, fullText.length));
    return { start, end };
  }
  function getSelectionPopoverPosition(range: Range, container: HTMLElement): { left: number; top: number } {
    const rect = range.getBoundingClientRect();
    const crect = container.getBoundingClientRect();
    const left = Math.max(8, rect.right - crect.left + 8);
    const top = Math.max(8, rect.top - crect.top - 8);
    return { left, top };
  }

  // Flow slide text overflow measurement for multi-line ellipsis indicator
  const textRefs = React.useRef<{ [id: string]: HTMLDivElement | null }>({});
  const [overflowMap, setOverflowMap] = React.useState<{ [id: string]: boolean }>({});
  const recomputeOverflow = React.useCallback(() => {
    const next: { [id: string]: boolean } = {};
    try {
      (versions || []).forEach((v) => {
        const el = textRefs.current[v.id];
        if (el) next[v.id] = el.scrollHeight > el.clientHeight + 1;
      });
    } catch {}
    setOverflowMap(next);
  }, [versions]);
  React.useEffect(() => {
    recomputeOverflow();
  }, [recomputeOverflow, flowIndex, isFullscreen]);
  React.useEffect(() => {
    const id = window.setTimeout(() => recomputeOverflow(), 0);
    return () => window.clearTimeout(id);
  }, [versions, flowIndex, isFullscreen, recomputeOverflow]);
  React.useEffect(() => {
    const onResize = () => recomputeOverflow();
    window.addEventListener('resize', onResize);
    const id = window.setInterval(onResize, 600);
    return () => { window.removeEventListener('resize', onResize); window.clearInterval(id); };
  }, [recomputeOverflow]);

  const uploadSourceDoc = async (file: File) => {
    setAddingSourceUploading(true);
    try {
      const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
      const form = new FormData();
      form.append('file', file);
      const parseEndpoint = getTutorialMode() ? '/api/tutorial-refinity/parse' : '/api/refinity/parse';
      const resp = await fetch(`${base}${parseEndpoint}`, { method: 'POST', body: form });
      const data = await resp.json().catch(()=>({}));
      if (!resp.ok) throw new Error(data?.error || 'Failed to parse document');
      setNewTaskSource(String(data?.text || ''));
    } finally {
      setAddingSourceUploading(false);
    }
  };

  const saveNewTask = async () => {
    const title = newTaskTitle.trim();
    const src = newTaskSource.trim();
    if (!title || !src) return;
    try {
      const body = { title, sourceText: src, createdBy: username };
      if (getTutorialMode()) {
        // In tutorial mode, include week number
        const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
        (body as any).weekNumber = weekNumber;
      }
      const token = localStorage.getItem('token');
      const user = JSON.parse(localStorage.getItem('user') || '{}');
      const viewMode = (localStorage.getItem('viewMode') || 'auto');
      const effectiveRole = viewMode === 'student' ? 'student' : (user.role || 'visitor');
      
      const headers: any = { 'Content-Type': 'application/json', 'x-user-name': username };
      if (token) {
        headers['Authorization'] = `Bearer ${token}`;
      }
      // Always send role header
      headers['x-user-role'] = effectiveRole;
      headers['user-role'] = effectiveRole; // Also send without x- prefix for compatibility
      
      const resp = await fetch(getApiBase('/tasks'), { method: 'POST', headers, body: JSON.stringify(body) });
      const saved = await resp.json().catch(()=>({}));
      if (!resp.ok) throw new Error(saved?.error || 'Save failed');
      const t: Task = { id: saved._id, title: saved.title, sourceText: saved.sourceText, createdBy: saved.createdBy };
      setTasks(prev => [...prev, t]);
      setSelectedTaskId(t.id);
      setShowAddTask(false);
      setNewTaskTitle('');
      setNewTaskSource('');
    } catch {}
  };

  const assignRandom = async () => {
    try {
      const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
      // Load tasks list - use getApiBase to route to correct API
      const respTasks = await fetch(`${base}${getApiBase('/tasks')}`);
      const tasksData: any[] = await respTasks.json().catch(()=>[]);
      const taskList: Task[] = Array.isArray(tasksData) ? tasksData.map(d => ({ id: d._id, title: d.title, sourceText: d.sourceText, createdBy: d.createdBy })) : [];
      if (!taskList.length) { setTaskStageNote('No tasks available yet.'); return; }
      const randTask = taskList[Math.floor(Math.random() * taskList.length)];
      // Load versions for that task - use getApiBase to route to correct API
      const respVers = await fetch(`${base}${getApiBase(`/tasks/${encodeURIComponent(randTask.id)}/versions`)}`);
      const versData: any[] = await respVers.json().catch(()=>[]);
      const vers: Version[] = Array.isArray(versData) ? versData.map(d => ({ id: d._id, taskId: d.taskId, originalAuthor: d.originalAuthor, revisedBy: d.revisedBy, versionNumber: d.versionNumber, content: d.content, parentVersionId: d.parentVersionId })) : [];
      if (!vers.length) { setTaskStageNote('No versions found to revise.'); return; }
      // Prefer versions not by current user; fallback to any
      const eligible = vers.filter(v => (v.originalAuthor !== username && v.revisedBy !== username));
      const pool = eligible.length ? eligible : vers;
      const pick = pool[Math.floor(Math.random() * pool.length)];
      // Switch to that task context and open editor
      setSelectedTaskId(randTask.id);
      setVersions(vers);
      setCurrentVersionId(pick.id);
      // Ensure EditorPane receives correct initial translation by waiting one tick
      setTimeout(() => setStage('editor'), 0);
    } catch {
      setTaskStageNote('Random pick failed. Please try again.');
    }
  };

  const submitPastedTranslation = async () => {
    const text = (pastedTranslation || '').trim();
    if (!text || !task?.id) return;
    try {
      const resp = await fetch(getApiBase(`/tasks/${encodeURIComponent(task.id)}/versions`), { 
        method: 'POST', 
        headers: { 'Content-Type': 'application/json' }, 
        body: JSON.stringify({ originalAuthor: username, revisedBy: undefined, content: text }) 
      });
      const saved = await resp.json().catch(()=>({}));
      if (!resp.ok) {
        console.error('Failed to save version:', saved?.error || 'Unknown error', saved);
        throw new Error(saved?.error || 'Failed to save version');
      }
      const newVersion: Version = { 
        id: saved._id, 
        taskId: saved.taskId, 
        originalAuthor: saved.originalAuthor, 
        revisedBy: saved.revisedBy, 
        versionNumber: saved.versionNumber, 
        content: saved.content, 
        parentVersionId: saved.parentVersionId 
      };
      setVersions(prev => [...prev, newVersion]);
      // Update cache
      try {
        const cacheKey = versionsCacheKey(task.id);
        const cached = JSON.parse(sessionStorage.getItem(cacheKey) || '[]');
        const updated = [...cached, newVersion];
        sessionStorage.setItem(cacheKey, JSON.stringify(updated));
      } catch {}
      setPastedTranslation('');
      setCurrentVersionId(newVersion.id);
      setStage('flow');
    } catch (e) {
      console.error('Error saving draft translation:', e);
    }
  };

  const selectManual = (id: string) => {
    setCurrentVersionId(id);
    setStage('editor');
  };

  const deleteTask = React.useCallback(async () => {
    try {
      if (!task?.id) return;
      const ok = window.confirm('Delete this Deep Revision task and all its versions? This cannot be undone.');
      if (!ok) return;
      const headers: any = { 'x-user-name': username };
      if (isAdmin) headers['x-user-role'] = 'admin';
      const resp = await fetch(getApiBase(`/tasks/${encodeURIComponent(task.id)}`), { method: 'DELETE', headers });
      if (!resp.ok) throw new Error('Delete failed');
      setTasks(prev => prev.filter(t => t.id !== task.id));
      setVersions([]);
      // Stay on landing; do not auto-navigate into flow for another task
      const remaining = tasks.filter(t => t.id !== (task?.id || ''));
      setSelectedTaskId(remaining.length ? remaining[0].id : '');
      appliedInitialRouteRef.current = true; // prevent any hash restore from forcing stage
      initialRouteRef.current = null;
      setStage('task');
    } catch {}
  }, [isAdmin, task?.id, tasks, username]);

  const handleSaveRevision = React.useCallback(async (newContent: string) => {
    const parent = taskVersions.find(v => v.id === currentVersionId);
    if (!task?.id) return;
    try {
      const resp = await fetch(getApiBase(`/tasks/${encodeURIComponent(task.id)}/versions`), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          originalAuthor: parent?.originalAuthor || username,
          revisedBy: username,
          content: newContent,
          parentVersionId: parent?.id,
        })
      });
      const saved = await resp.json().catch(()=>({}));
      if (!resp.ok) {
        console.error('Failed to save revision:', saved?.error || 'Unknown error', saved);
        throw new Error(saved?.error || 'Save failed');
      }
      const v: Version = {
        id: saved._id,
        taskId: saved.taskId,
        originalAuthor: saved.originalAuthor,
        revisedBy: saved.revisedBy,
        versionNumber: saved.versionNumber,
        content: saved.content,
        parentVersionId: saved.parentVersionId,
      };
      setVersions(prev => [...prev, v]);
      // Update cache
      try {
        const cacheKey = versionsCacheKey(task.id);
        const cached = JSON.parse(sessionStorage.getItem(cacheKey) || '[]');
        const updated = [...cached, v];
        sessionStorage.setItem(cacheKey, JSON.stringify(updated));
      } catch {}
      setCurrentVersionId(v.id);
      // Navigate to flow to view the new version without clearing selection
      setStage('flow');
    } catch (e) {
      console.error('Error saving revision:', e);
      // no-op; keep user in editor if needed
    }
  }, [task, taskVersions, currentVersionId, username, getApiBase, versionsCacheKey, getTutorialMode]);

  return (
    <div className={isFullscreen ? 'fixed inset-0 z-50 bg-white overflow-auto' : 'relative'}>
      {isFullscreen && (
        <div className="sticky top-0 z-50 flex justify-end p-3 bg-white/80 backdrop-blur">
          <button onClick={() => setIsFullscreen(false)} className="px-3 py-1.5 text-sm rounded-2xl border border-gray-200 bg-white">Exit Full Screen</button>
        </div>
      )}
      {/* Annotation modal (Version Flow only) */}
      {annotationModalOpen && annotationPopover && (
        <div className="fixed inset-0 z-[5000] flex items-center justify-center bg-black/40">
          <div className="bg-white rounded-xl shadow-xl w-full max-w-md p-6">
            <h3 className="text-lg font-semibold text-gray-900 mb-4">{editingAnnotation ? 'Edit annotation' : 'New annotation'}</h3>
            <div className="space-y-4">
              <div>
                <label className="block text-sm text-gray-700 mb-1">Category <span className="text-red-500">*</span></label>
                <select
                  value={modalCategory}
                  onChange={(e)=>setModalCategory(e.target.value as AnnotationCategory)}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm"
                >
                  {(['distortion','omission','register','unidiomatic','grammar','spelling','punctuation','addition','other'] as AnnotationCategory[]).map(k=>(
                    <option key={k} value={k}>{k==='unidiomatic' ? 'Unidiomatic expression' : k==='grammar' ? 'Error of grammar, syntax' : k==='spelling' ? 'Error of spelling' : k==='punctuation' ? 'Error of punctuation' : k==='addition' ? 'Unjustified addition' : k==='omission' ? 'Unjustified omission' : k.charAt(0).toUpperCase()+k.slice(1)}</option>
                  ))}
                </select>
              </div>
              <div>
                <label className="block text-sm text-gray-700 mb-1">Comment (optional)</label>
                <textarea
                  value={modalComment}
                  onChange={(e)=>setModalComment(e.target.value)}
                  rows={3}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm"
                  placeholder="Enter the correction or context…"
                />
              </div>
            </div>
            <div className="mt-6 flex items-center justify-between">
              {editingAnnotation && (
                <button
                  onClick={()=>{
                    if (editingAnnotation) deleteAnnotationById(editingAnnotation.id);
                    setAnnotationModalOpen(false);
                    setAnnotationPopover(null);
                    setEditingAnnotation(null);
                  }}
                  className="px-3 py-1.5 text-sm rounded-lg text-white bg-red-600 hover:bg-red-700"
                >
                  Delete
                </button>
              )}
              <div className="ml-auto flex gap-2">
                <button onClick={()=>{ setAnnotationModalOpen(false); setAnnotationPopover(null); setEditingAnnotation(null); }} className="px-3 py-1.5 text-sm rounded-lg border border-gray-300 text-gray-700 bg-white hover:bg-gray-50">Cancel</button>
                <button
                  onClick={()=>{
                    const versionId = annotationPopover.versionId;
                    if (editingAnnotation) {
                      const updated: Annotation = { ...editingAnnotation, category: modalCategory, comment: modalComment, updatedAt: Date.now() };
                      updateAnnotation(updated);
                    } else {
                      const range = annotationPopover.range;
                      const a: Annotation = {
                        id: `a_${Date.now()}_${Math.random().toString(36).slice(2,7)}`,
                        versionId,
                        start: range.start,
                        end: range.end,
                        category: modalCategory,
                        comment: modalComment,
                        createdAt: Date.now(),
                        updatedAt: Date.now(),
                      };
                      addAnnotation(a);
                    }
                    setAnnotationModalOpen(false);
                    setAnnotationPopover(null);
                    setEditingAnnotation(null);
                  }}
                  className="px-3 py-1.5 text-sm rounded-lg text-white bg-indigo-600 hover:bg-indigo-700"
                >
                  Save
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
      {/* Clean container (no purple gradient) */}
      <div className={isFullscreen ? 'relative mx-auto max-w-6xl p-6' : 'relative rounded-xl p-6 bg-transparent'}>
      {/* Stage 1: Task Creation / Selection */}
      {stage === 'task' && (
        <div>
          <div className="mb-2" />
          {/* Task selector (full width) */}
          <div className="mb-6">
            <label className="block text-sm text-gray-700 mb-1">Task</label>
            <div className="relative inline-block w-full md:w-80">
              <button type="button" onClick={()=>setTaskMenuOpen(o=>!o)} className="relative w-full text-left inline-flex items-center justify-between px-3 py-1.5 rounded-2xl border border-gray-200 ring-1 ring-inset ring-gray-200 backdrop-blur-md bg-white/30 text-sm">
                <span className="truncate text-gray-900">{tasks.find(t=>t.id===selectedTaskId)?.title || 'Select a task'}</span>
                <span className="ml-2 text-gray-700">▾</span>
              </button>
              {taskMenuOpen && (
                <div className="absolute z-20 mt-2 w-full">
                  <div className="relative rounded-2xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-gray-200 border border-gray-200 shadow-lg overflow-hidden">
                    <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
                    <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
                    <ul className="relative max-h-64 overflow-auto text-sm">
                      {tasks.map(t => (
                        <li key={t.id}>
                          <button type="button" onClick={()=>{ 
                            userSelectedTaskIdRef.current = t.id;
                            setSelectedTaskId(t.id);
                            setTaskMenuOpen(false);
                            // Persist user selection to localStorage in tutorial mode
                            try {
                              const isTutorial = localStorage.getItem('refinityMode') === 'tutorial';
                              if (isTutorial) {
                                const weekNumber = parseInt(localStorage.getItem('tutorialWeekNumber') || '0');
                                if (weekNumber > 0) {
                                  localStorage.setItem(`tutorial_selected_task_week_${weekNumber}`, t.id);
                                }
                              }
                            } catch {}
                          }} className={`w-full text-left px-3 py-1.5 text-gray-900 hover:bg-white/30 ${selectedTaskId===t.id ? 'bg-indigo-600/20' : ''}`}>{t.title}</button>
                        </li>
                      ))}
                      {tasks.length===0 && (
                        <li className="px-3 py-2 text-sm text-gray-600">No tasks yet</li>
                      )}
                    </ul>
                  </div>
                </div>
              )}
            </div>
          </div>

          {/* Unified input area: tabs + content in one container */}
          <div className="mb-6 rounded-2xl border border-gray-200 bg-white/60 backdrop-blur">
            <div className="px-3 py-2 border-b border-gray-200">
              <div className="inline-flex rounded-xl overflow-hidden ring-1 ring-gray-200">
                <button onClick={()=>setInputTab('paste')} className={`px-3 py-1.5 text-sm ${inputTab==='paste' ? 'bg-white text-gray-900' : 'bg-gray-100 text-gray-600'}`}>Paste Text</button>
                <button onClick={()=>setInputTab('upload')} className={`px-3 py-1.5 text-sm ${inputTab==='upload' ? 'bg-white text-gray-900' : 'bg-gray-100 text-gray-600'}`}>Upload Document</button>
              </div>
            </div>
            <div className="p-4" style={{ minHeight: '210px' }}>
              {inputTab==='paste' ? (
                <div>
                  <textarea value={pastedTranslation} onChange={(e)=>setPastedTranslation(e.target.value)} rows={4} className="w-full px-4 py-3 border border-ui-border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white" placeholder="Paste translation text here…" />
                  <div className="mt-2">
                    <button onClick={submitPastedTranslation} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-slate-800 hover:bg-slate-900 active:translate-y-0.5 transition-all duration-200">Use pasted text</button>
                  </div>
                </div>
              ) : (
                <div>
                  <input ref={fileInputRef} type="file" accept=".doc,.docx" onChange={(e)=>{ const f=e.target.files?.[0]; if(f) uploadDocx(f); if(fileInputRef.current) fileInputRef.current.value=''; }} className="hidden" />
                  <button type="button" onClick={()=>fileInputRef.current?.click()} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-slate-800 hover:bg-slate-900 active:translate-y-0.5 transition-all duration-200">
                    <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
                    <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
                    <span className="relative z-10">Choose file</span>
                  </button>
                  {uploading && <span className="ml-3 text-sm text-gray-600">Uploading…</span>}
                </div>
              )}
            </div>
          </div>

          <div className="mt-8 flex gap-3 items-center flex-wrap">
            <button
              onClick={()=>{ 
                if (taskVersions.length > 0) { 
                  setStage('flow'); 
                } else { 
                  // In tutorial mode, admin should have created the first version
                  const isTut = getTutorialMode();
                  if (isTut) {
                    setTaskStageNote('No versions found. Please contact admin to create the first version.');
                  } else {
                    setTaskStageNote('No versions found to start.');
                  }
                } 
              }}
              className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-violet-600/70 hover:bg-violet-700 active:translate-y-0.5 transition-all duration-200"
            >
              <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
              <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
              <span className="relative z-10">Start</span>
            </button>
            {(!getTutorialMode() || isAdmin) && (
            <button onClick={()=>setShowAddTask(v=>!v)} className="relative inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-violet-700 ring-1 ring-inset ring-violet-300 bg-white/20 backdrop-blur-md hover:bg-violet-50 active:translate-y-0.5 transition-all duration-200">
              <span className="relative z-10">New Task</span>
            </button>
            )}
            {(isAdmin || String(username).toLowerCase()===String(tasks.find(t=>t.id===selectedTaskId)?.createdBy||'').toLowerCase()) && task?.id && (
              <div className="flex items-center gap-2">
                <button onClick={openEditTask} className="relative inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-gray-800 ring-1 ring-inset ring-gray-300 bg-white/20 backdrop-blur-md hover:bg-gray-100 active:translate-y-0.5 transition-all duration-200">
                  <span className="relative z-10">Edit Task</span>
                </button>
                <button onClick={deleteTask} className="relative inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-red-700 ring-1 ring-inset ring-red-300 bg-white/20 backdrop-blur-md hover:bg-red-600 hover:text-white active:translate-y-0.5 transition-all duration-200">
                  <span className="relative z-10">Delete Task</span>
                </button>
              </div>
            )}
            {taskStageNote && <span className="text-sm text-gray-600 ml-2">{taskStageNote}</span>}
          </div>

          {showAddTask && (
            <div className="mt-6 relative rounded-xl">
              <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-indigo-200/45 via-indigo-100/40 to-indigo-300/45" />
              <div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-4">
                <div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
                <div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
                <div className="relative grid grid-cols-1 md:grid-cols-3 gap-4 items-start">
                  <div>
                    <label className="block text-sm text-gray-700 mb-1">Title</label>
                    <input value={newTaskTitle} onChange={(e)=>setNewTaskTitle(e.target.value)} className="w-full px-3 py-2 rounded-lg bg-white/50 backdrop-blur border border-ui-border text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" />
                  </div>
                  <div className="md:col-span-2">
                    <label className="block text-sm text-gray-700 mb-1">Source Text</label>
                    <textarea value={newTaskSource} onChange={(e)=>setNewTaskSource(e.target.value)} rows={4} className="w-full px-4 py-3 border border-ui-border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white" placeholder="Paste source text here…" />
                    <div className="mt-2 flex items-center gap-3">
                      <input ref={addTaskFileRef} type="file" accept=".doc,.docx" onChange={(e)=>{ const f=e.target.files?.[0]; if(f) uploadSourceDoc(f); if(addTaskFileRef.current) addTaskFileRef.current.value=''; }} className="hidden" />
                      <button type="button" onClick={()=>addTaskFileRef.current?.click()} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-indigo-600/70 active:translate-y-0.5 transition-all duration-200">
                        <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
                        <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
                        <span className="relative z-10">Choose source file</span>
                      </button>
                      {addingSourceUploading && <span className="text-sm text-gray-600">Parsing…</span>}
                    </div>
                  </div>
                </div>
                <div className="relative mt-4 flex gap-3">
                  <button onClick={saveNewTask} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-indigo-600/70 active:translate-y-0.5 transition-all duration-200">Save Task</button>
                  <button onClick={()=>{ setShowAddTask(false); setNewTaskTitle(''); setNewTaskSource(''); }} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200">Cancel</button>
                </div>
              </div>
            </div>
          )}
        </div>
      )}
      {/* Simple Edit Task Modal */}
      {editingTaskOpen && (
        <div className="fixed inset-0 z-[5000] flex items-center justify-center bg-black/40">
          <div className="bg-white rounded-xl shadow-xl w-full max-w-2xl p-6">
            <h3 className="text-lg font-semibold text-gray-900 mb-4">Edit Task</h3>
            <div className="space-y-4">
              <div>
                <label className="block text-sm text-gray-700 mb-1">Title</label>
                <input value={editTaskTitle} onChange={e=>setEditTaskTitle(e.target.value)} className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" />
              </div>
              <div>
                <label className="block text-sm text-gray-700 mb-1">Source Text</label>
                <textarea value={editTaskSource} onChange={e=>setEditTaskSource(e.target.value)} className="w-full border border-gray-300 rounded-lg px-3 py-2 min-h-[200px] focus:outline-none focus:ring-2 focus:ring-indigo-500" />
              </div>
            </div>
            <div className="mt-6 flex items-center justify-end gap-3">
              <button onClick={()=>setEditingTaskOpen(false)} className="px-4 py-2 text-sm rounded-lg border border-gray-300 text-gray-700 bg-white hover:bg-gray-50">Cancel</button>
              <button onClick={saveTaskEdit} disabled={savingTaskEdit} className="px-4 py-2 text-sm rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 disabled:opacity-60">{savingTaskEdit ? 'Saving...' : 'Save'}</button>
            </div>
          </div>
        </div>
      )}

      {/* Stage 2: Version Flow - Swiper Coverflow */}
      {stage === 'flow' && (
        <div>
          <div className="flex items-center justify-between mb-4">
            <div>
              <h3 className="text-xl font-semibold text-black">Version Flow — {task?.title}</h3>
              <div className="text-gray-700 text-sm">Swipe/click to browse. Center slide is active.</div>
            </div>
            <div className="flex gap-2">
              <button onClick={()=>{ setIsFullscreen(false); setStage('task'); }} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200">
                <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
                <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
                <span className="relative z-10">Back</span>
              </button>
              <button onClick={()=>setIsFullscreen(v=>!v)} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200">
                <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
                <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
                <span className="relative z-10">{isFullscreen ? 'Windowed' : 'Full Screen'}</span>
              </button>
            </div>
          </div>
          <div className={`relative min-h-0 ${isFullscreen ? 'pt-1 pb-2' : 'pt-2 pb-6'}`} style={{ maxHeight: (compareUIOpen || revDownloadOpen) ? 'none' : (isFullscreen ? 'calc(100vh - 80px)' : 'calc(100vh - 160px)') }}>
            <style>{`
              .mySwiper .swiper-pagination { z-index: 5; pointer-events: none; }
              .mySwiper .swiper-pagination-bullet { pointer-events: auto; }
              .mySwiper .swiper-slide { z-index: 1; }
              .mySwiper .swiper-slide-active { z-index: 1000; }
              .mySwiper .swiper-slide-next, .mySwiper .swiper-slide-prev { z-index: 500; }
            `}</style>
            <SwiperRoot
              modules={[EffectCoverflow, Navigation, Pagination, A11y]}
              effect="coverflow"
              onSlideChange={(s: SwiperInstance)=> setFlowIndex(s.activeIndex)}
              onAfterInit={(s: SwiperInstance)=> setFlowIndex(s.activeIndex)}
              onClickCapture={(e: any)=>{ /* keep default; no slide-level click */ }}
              navigation
              pagination={{ clickable: true }}
              centeredSlides
              // fixed height so cards fit viewport and never overflow page
              slidesPerView={'auto'}
              spaceBetween={32}
              preventClicks={false}
              preventClicksPropagation={false}
              noSwiping={true}
              noSwipingClass={'swiper-no-swiping'}
              breakpoints={{
                640: { spaceBetween: 36 },
                1024: { spaceBetween: 40 },
                1440: { spaceBetween: 48 }
              }}
              observer
              observeParents
              allowTouchMove={false}
              coverflowEffect={{ rotate: 0, stretch: 0, depth: 80, modifier: 1, slideShadows: false }}
              className="mySwiper !px-4 overflow-visible pb-2"
              style={{ height: isFullscreen ? 'calc(100vh - 96px)' : 'min(76vh, calc(100vh - 220px))', minHeight: '340px' }}
            >
      {taskVersions.map((v, idx) => {
                const isCenter = idx === flowIndex;
                return (
                  <SwiperSlide key={v.id} style={{ width: 'clamp(320px, 48vw, 720px)', height: isFullscreen ? '72vh' : '62.4vh', zIndex: (idx === flowIndex ? 1000 : (Math.abs(idx - flowIndex) === 1 ? 500 : 1)) }}>
                      <div 
                      className={`relative h-full rounded-2xl p-6 bg-white ring-1 ring-gray-200 shadow-xl text-base overflow-hidden flex flex-col ${isCenter ? 'z-[60]' : 'opacity-95 z-0'}`}
                      role="button"
                      onClick={(e)=>{ e.preventDefault(); setPreviewVersionId(v.id); setShowPreviewDiff(false); setStage('preview'); }}
                    >
                      <div className={`ref-card-overlay pointer-events-none absolute inset-0 rounded-2xl opacity-40 [background:linear-gradient(to_bottom,rgba(255,255,255,0.45),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]`} />
                      <div className={`ref-card-overlay pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-30`} />
                      <div className="text-gray-800 text-sm mb-2">Original: {v.originalAuthor}</div>
                      <div className="text-gray-600 text-xs mb-3">Revised by: {v.revisedBy ? `${v.revisedBy} (v${v.versionNumber})` : `— (v${v.versionNumber})`}</div>
                      <div
                        ref={(el)=>{ textRefs.current[v.id]=el; }}
                        className={`text-gray-900 whitespace-pre-wrap break-words leading-relaxed flex-1 overflow-hidden pr-1 relative`}
                        style={{ maxHeight: 'calc(100% - 10.5rem)', paddingBottom: '0.25rem' }}
                      >
                        {v.content || ''}
                      </div>
                      {overflowMap[v.id] && (
                        <div className="pointer-events-none absolute text-gray-700" style={{ left: '1.5rem', bottom: '6rem', zIndex: 2000 }}>…</div>
                      )}
                      {isCenter && (
                        <div
                          className="action-row absolute left-6 right-6 bottom-6 swiper-no-swiping"
                          data-version-number={v.versionNumber}
                          data-version-index={idx}
                          data-version-id={v.id}
                          data-owner={String(v.revisedBy || v.originalAuthor || '').toLowerCase() === String(username).toLowerCase() || !!isAdmin}
                          onClickCapture={(e)=> {
                            // Diagnostic: capture clicks within action row to ensure they are not swallowed by slide
                            const t = e.target as HTMLElement;
                            console.debug('[VF] action-row click(capture)', {
                              idx,
                              versionId: v.id,
                              buttonData: t?.dataset || {},
                              targetTag: t?.tagName,
                              targetClasses: t?.className
                            });
                          }}
                          onMouseEnter={()=>{ updateReviseOverlay(); }}
                          onMouseMove={()=>{ updateReviseOverlay(); }}
                          style={{ pointerEvents: 'auto', transform: 'translateZ(0)', zIndex: 3000 }}
                        >
                          {/* Invisible first child to bypass first-child quirks while matching pill styles (kept in place) */}
                          <button
                            type="button"
                            aria-hidden="true"
                            tabIndex={-1}
                            className="inline-flex items-center justify-center gap-2 text-white text-sm font-medium rounded-2xl ring-1 ring-inset ring-white/50"
                            style={{ opacity: 0, pointerEvents: 'none', position: 'absolute', left: 0, top: 0, padding: '0.5rem 0.75rem', backgroundColor: '#4f46e5', borderRadius: '1rem', border: '1px solid rgba(255,255,255,0.5)' }}
                          />
                          <div className="flex justify-center gap-3 w-full">
                            <button
                              type="button"
                              data-btn="revise"
                              ref={(el)=>{ reviseBtnRefs.current[v.id] = el; }}
                              onClickCapture={(e)=>{ e.stopPropagation(); }}
                              onPointerDown={(e)=>{ e.stopPropagation(); }}
                              onPointerUp={(e)=>{ e.stopPropagation(); }}
                              onClick={(e) => {
                                e.preventDefault();
                                e.stopPropagation();
                                console.debug('[VF] Revise click', { idx, versionId: v.id, versionNumber: v.versionNumber });
                                selectManual(v.id);
                              }}
                              onMouseEnter={(e) => {
                                // Diagnostic: inspect computed styles which could affect clickability
                                try {
                                  const el = e.currentTarget as HTMLElement;
                                  const elCS = window.getComputedStyle(el);
                                  const parent = el.parentElement;
                                  const parentCS = parent ? window.getComputedStyle(parent) : null;
                                  const row = el.closest('.action-row') as HTMLElement | null;
                                  const rowCS = row ? window.getComputedStyle(row) : null;
                                  console.debug('[VF] Revise hover styles', {
                                    idx,
                                    versionId: v.id,
                                    el: { zIndex: elCS.zIndex, pointerEvents: elCS.pointerEvents, position: elCS.position },
                                    parent: parentCS ? { zIndex: parentCS.zIndex, pointerEvents: parentCS.pointerEvents, position: parentCS.position } : null,
                                    row: rowCS ? { zIndex: rowCS.zIndex, pointerEvents: rowCS.pointerEvents, position: rowCS.position } : null
                                  });
                                } catch {}
                              }}
                              className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-violet-600/70 hover:bg-violet-700 active:translate-y-0.5 transition-all duration-200"
                              style={{ position: 'relative', zIndex: 3100, pointerEvents: 'auto', cursor: 'pointer', touchAction: 'manipulation' }}
                            >
                              <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
                              <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
                              Revise
                            </button>
                          {(String(v.revisedBy || v.originalAuthor || '').toLowerCase() === String(username).toLowerCase() || isAdmin) && (
                            <button
                              type="button"
                              data-btn="edit"
                              onClick={(e)=>{ e.preventDefault(); e.stopPropagation(); console.debug('[VF] Edit click', { idx, versionId: v.id }); setCurrentVersionId(v.id); setEditingVersionId(v.id); setStage('editor'); }}
                              className="relative inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-gray-800 ring-1 ring-inset ring-gray-300 bg-white/20 backdrop-blur-md hover:bg-gray-100 active:translate-y-0.5 transition-all duration-200"
                              style={{ position: 'relative', zIndex: 3100, pointerEvents: 'auto', cursor: 'pointer', touchAction: 'manipulation' }}
                            >
                              Edit
                            </button>
                          )}
                            <button
                              type="button"
                              data-btn="download"
                              onClick={(e)=>{
                                e.preventDefault(); e.stopPropagation();
                                // Export single version content as plain .docx (same as Editor “Without Annotations”)
                                (async()=>{
                                  try {
                                    const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                                    const currentUser = username || 'User';
                                    const filename = `${toSafeName(task?.title||'Task')}_${yyyymmdd_hhmm()}_${toSafeName(currentUser)}.docx`;
                                    const body = { current: v.content || '', filename };
                                    const resp = await fetch(`${base}/api/refinity/export-plain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                                    if (!resp.ok) throw new Error('Export failed');
                                    const blob = await resp.blob();
                                    const url = window.URL.createObjectURL(blob);
                                    const link = document.createElement('a');
                                    link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                                  } catch {}
                                })();
                              }}
                              className="relative inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-gray-800 ring-1 ring-inset ring-gray-300 bg-white/20 backdrop-blur-md hover:bg-gray-100 active:translate-y-0.5 transition-all duration-200"
                              style={{ position: 'relative', zIndex: 3100, pointerEvents: 'auto', cursor: 'pointer', touchAction: 'manipulation' }}
                            >
                              Download
                            </button>
                            <button 
                              type="button"
                              data-btn="compare"
                              onClick={(e) => { e.preventDefault(); e.stopPropagation(); console.debug('[VF] Compare click', { idx, versionId: v.id }); setCompareUIOpen(true); if (!compareA) setCompareA(v.id); }} 
                              className="relative inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-gray-800 ring-1 ring-inset ring-gray-300 bg-white/20 backdrop-blur-md hover:bg-gray-100 active:translate-y-0.5 transition-all duration-200"
                              style={{ position: 'relative', zIndex: 3100, pointerEvents: 'auto', cursor: 'pointer', touchAction: 'manipulation' }}
                            >
                              Compare
                            </button>
                            {(isAdmin || String(v.revisedBy || v.originalAuthor || '').toLowerCase() === String(username).toLowerCase()) && (
                              <button
                                type="button"
                                data-btn="delete"
                                onClick={(e)=>{ e.preventDefault(); e.stopPropagation(); console.debug('[VF] Delete click', { idx, versionId: v.id }); deleteVersion(v.id); }}
                                className="relative inline-flex items-center justify-center gap-2 px-3 py-1.5 text-sm font-medium rounded-2xl text-red-700 ring-1 ring-inset ring-red-300 bg-white/20 backdrop-blur-md hover:bg-red-600 hover:text-white active:translate-y-0.5 transition-all duration-200"
                                style={{ position: 'relative', zIndex: 3100, pointerEvents: 'auto', cursor: 'pointer', touchAction: 'manipulation' }}
                              >
                                Delete
                              </button>
                            )}
                          </div>
                        </div>
                      )}
                    </div>
                  </SwiperSlide>
                );
              })}
            </SwiperRoot>
            {reviseOverlay && createPortal(
              <button
                aria-label="Revise overlay"
                style={{
                  position: 'fixed',
                  left: `${reviseOverlay.left}px`,
                  top: `${reviseOverlay.top}px`,
                  width: `${reviseOverlay.width}px`,
                  height: `${reviseOverlay.height}px`,
                  background: 'transparent',
                  border: 'none',
                  padding: 0,
                  margin: 0,
                  cursor: 'pointer',
                  zIndex: 2147483000
                }}
                onClick={(e)=>{ e.preventDefault(); e.stopPropagation(); selectManual(reviseOverlay.vid); }}
                onPointerDown={(e)=>{ e.stopPropagation(); }}
                onPointerUp={(e)=>{ e.stopPropagation(); }}
              />,
              document.body
            )}
            
            {/* External V2 Revise Button removed per user request */}
            
            {compareUIOpen && (
              <div className="mt-5 p-3 rounded-2xl border border-gray-200 bg-white/60 backdrop-blur" style={{ position: 'relative' }}>
                <div className="flex flex-col md:flex-row gap-3 md:items-end">
                  <div className="flex-1">
                    <label className="block text-sm text-gray-700 mb-1">Version A (previous)</label>
                    <select value={compareA} onChange={(e)=>setCompareA(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm">
                      <option value="">Select version…</option>
                      {taskVersions.map(v => (
                        <option key={v.id} value={v.id}>{`v${v.versionNumber} — ${v.revisedBy || v.originalAuthor}`}</option>
                      ))}
                    </select>
                  </div>
                  <div className="flex-1">
                    <label className="block text-sm text-gray-700 mb-1">Version B (current)</label>
                    <select value={compareB} onChange={(e)=>setCompareB(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm">
                      <option value="">Select version…</option>
                      {taskVersions.map(v => (
                        <option key={v.id} value={v.id}>{`v${v.versionNumber} — ${v.revisedBy || v.originalAuthor}`}</option>
                      ))}
                    </select>
                  </div>
                  <div className="flex gap-2 overflow-visible items-start">
                    <button onClick={()=>{ const tmp = compareA; setCompareA(compareB); setCompareB(tmp); }} disabled={!compareA && !compareB} className="px-3 py-2 text-sm rounded-md border border-gray-300 bg-white">Swap</button>
                    <button onClick={async()=>{
                      const a = taskVersions.find(v=>v.id===compareA);
                      const b = taskVersions.find(v=>v.id===compareB);
                      if (!a || !b || a.id===b.id) return;
                      try {
                        setCompareLoading(true);
                        const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                        const resp = await fetch(`${base}/api/refinity/diff`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prev: a.content || '', current: b.content || '' }) });
                        const data = await resp.json().catch(()=>({}));
                        if (!resp.ok) throw new Error(data?.error || 'Diff failed');
                        setCompareDiffHtml(String(data?.html || ''));
                        setCompareModalOpen(true);
                        setLastDiffIds({ a: a.id, b: b.id });
                      } catch {}
                      finally { setCompareLoading(false); }
                    }} disabled={!compareA || !compareB || compareA===compareB || compareLoading} className="px-3 py-2 text-sm rounded-md border border-gray-300 bg-white disabled:opacity-50">{compareLoading ? 'Computing…' : 'Show Diff'}</button>
                    <div className="relative">
                      <button ref={compareBtnRef} onClick={(e)=>{ e.preventDefault(); const r=(e.currentTarget as HTMLElement).getBoundingClientRect(); setCompareMenuPos({ left: r.left, top: r.bottom + 8 }); setCompareDownloadOpen(v=>!v); }} disabled={!compareA || !compareB || compareA===compareB} className="px-3 py-2 text-sm rounded-md border border-gray-300 bg-white">Download ▾</button>
                      {compareDownloadOpen && compareMenuPos && createPortal(
                        <div style={{ position: 'fixed', left: compareMenuPos.left, top: compareMenuPos.top, zIndex: 10000 }} className="w-52 rounded-md border border-gray-200 bg-white shadow-lg text-left">
                          <button onClick={async()=>{
                            setCompareDownloadOpen(false);
                            const a = taskVersions.find(v=>v.id===compareA);
                            const b = taskVersions.find(v=>v.id===compareB);
                            if (!a || !b || a.id===b.id) return;
                            try {
                              const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                              const older = (a.versionNumber <= b.versionNumber) ? a : b;
                              const newer = (a.versionNumber <= b.versionNumber) ? b : a;
                              const latestReviser = (newer.revisedBy || newer.originalAuthor || username || 'User');
                              const A = older.versionNumber, B = newer.versionNumber;
                              const filename = `${toSafeName(task?.title||'Task')}_Compare-v${A}-v${B}_Inline_${yyyymmdd_hhmm()}_${toSafeName(latestReviser)}.docx`;
                              const body = { prev: older.content||'', current: newer.content||'', filename, authorName: latestReviser };
                              let resp = await fetch(`${base}/api/refinity/track-changes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                              if (!resp.ok) {
                                // Fallback to plain export of current text
                                resp = await fetch(`${base}/api/refinity/export-plain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current: b.content||'', filename }) });
                              }
                              if (!resp.ok) throw new Error('Export failed');
                              const blob = await resp.blob();
                              const url = window.URL.createObjectURL(blob);
                              const link = document.createElement('a');
                              link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                            } catch {}
                          }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Inline Changes</button>
                          <button onClick={async()=>{
                            setCompareDownloadOpen(false);
                            const a = taskVersions.find(v=>v.id===compareA);
                            const b = taskVersions.find(v=>v.id===compareB);
                            if (!a || !b || a.id===b.id) return;
                            try {
                              const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                              const older = (a.versionNumber <= b.versionNumber) ? a : b;
                              const newer = (a.versionNumber <= b.versionNumber) ? b : a;
                              const latestReviser = (newer.revisedBy || newer.originalAuthor || username || 'User');
                              const A = older.versionNumber, B = newer.versionNumber;
                              const filename = `${toSafeName(task?.title||'Task')}_Compare-v${A}-v${B}_Comments_${yyyymmdd_hhmm()}_${toSafeName(latestReviser)}.docx`;
                              const body = { prev: older.content||'', current: newer.content||'', filename, authorName: latestReviser, includeComments: false };
                              let resp = await fetch(`${base}/api/refinity/track-changes-ooxml`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                              if (!resp.ok) {
                                // Fallback to inline; then to plain as last resort
                                resp = await fetch(`${base}/api/refinity/track-changes-ooxml`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...body, includeComments: false }) });
                                if (!resp.ok) {
                                  resp = await fetch(`${base}/api/refinity/export-plain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current: b.content||'', filename }) });
                                }
                              }
                              if (!resp.ok) throw new Error('Export failed');
                              const blob = await resp.blob();
                              const url = window.URL.createObjectURL(blob);
                              const link = document.createElement('a');
                              link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                            } catch {}
                          }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Tracked Changes</button>
                          <button onClick={async()=>{
                            setCompareDownloadOpen(false);
                            const a = taskVersions.find(v=>v.id===compareA);
                            const b = taskVersions.find(v=>v.id===compareB);
                            if (!a || !b || a.id===b.id) return;
                            try {
                              const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/,'');
                              const older = (a.versionNumber <= b.versionNumber) ? a : b;
                              const newer = (a.versionNumber <= b.versionNumber) ? b : a;
                              const latestReviser = (newer.revisedBy || newer.originalAuthor || username || 'User');
                              const A = older.versionNumber, B = newer.versionNumber;
                              const filename = `${toSafeName(task?.title||'Task')}_Compare-v${A}-v${B}_CommentsSidebar_${yyyymmdd_hhmm()}_${toSafeName(latestReviser)}.docx`;
                              const body: any = {
                                prev: older.content || '',
                                current: newer.content || '',
                                filename,
                                authorName: latestReviser,
                                annotationVersionId: older.id,
                              };
                              let resp = await fetch(getApiBase('/compare-comments-with-corrections'), {
                                method: 'POST',
                                headers: (() => {
                                  const user = JSON.parse(localStorage.getItem('user') || '{}');
                                  const headers: any = { 'Content-Type': 'application/json' };
                                  if (user.name) headers['x-user-name'] = user.name;
                                  if (user.email) headers['x-user-email'] = user.email;
                                  if (user.role) {
                                    headers['x-user-role'] = user.role;
                                    headers['user-role'] = user.role;
                                  }
                                  return headers;
                                })(),
                                body: JSON.stringify(body),
                              });
                              if (!resp.ok) {
                                // Fallback to plain export of older text
                                resp = await fetch(getApiBase('/export-plain'), {
                                  method: 'POST',
                                  headers: { 'Content-Type': 'application/json' },
                                  body: JSON.stringify({ current: older.content || '', filename }),
                                });
                              }
                              if (!resp.ok) throw new Error('Export failed');
                              const blob = await resp.blob();
                              const url = window.URL.createObjectURL(blob);
                              const link = document.createElement('a');
                              link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                            } catch {}
                          }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Sidebar Comments</button>
                        </div>, document.body
                      )}
                    </div>
                    <button onClick={()=>{ setCompareUIOpen(false); setCompareA(''); setCompareB(''); }} className="px-3 py-2 text-sm rounded-md border border-gray-300 bg-white">Close</button>
                  </div>
                </div>
              </div>
            )}
            {compareUIOpen && (<div aria-hidden="true" className="h-10" />)}
            <style dangerouslySetInnerHTML={{ __html: `
              .mySwiper .swiper-wrapper { align-items: center; }
              .mySwiper .swiper-button-prev, .mySwiper .swiper-button-next { top: 50% !important; transform: translateY(-50%); }
              .mySwiper .swiper-button-prev:after, .mySwiper .swiper-button-next:after { font-size: 18px; color: #94a3b8; }
              .mySwiper .swiper-pagination { bottom: 6px; }
              /* Chrome-only: avoid half-line clipping at the bottom of slides */
              .is-chrome .mySwiper .swiper-slide { padding-bottom: 6px; }
              .is-chrome .mySwiper .swiper-slide .whitespace-pre-wrap { padding-bottom: 6px; }
              /* Ensure buttons inside slides are clickable */
              .mySwiper .swiper-slide button, .mySwiper .swiper-slide .action-row > * { 
                pointer-events: auto !important; 
                position: relative; 
                z-index: 9999 !important; 
                transform: translateZ(0) !important;
                -webkit-transform: translateZ(0) !important;
              }
              .mySwiper .swiper-slide button:hover, .mySwiper .swiper-slide .action-row > *:hover { transform: translateY(-0.5px) translateZ(0) !important; }
              /* Force button clickability on all slides */
              .mySwiper .swiper-slide-active button,
              .mySwiper .swiper-slide button,
              .mySwiper .swiper-slide .action-row > * {
                pointer-events: auto !important;
                z-index: 9999 !important;
              }
              /* Ensure first child in action row is clickable */
              .mySwiper .swiper-slide .action-row > *:first-child {
                pointer-events: auto !important;
                z-index: 99999 !important;
                position: relative !important;
                transform: translateZ(0) !important;
                -webkit-transform: translateZ(0) !important;
              }
            ` }} />
          </div>
          {compareModalOpen && (
            <div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center p-4" role="dialog" aria-modal="true" key="compare-modal">
              <div className="relative max-w-5xl w-full max-h-[85vh] rounded-2xl bg-white shadow-2xl ring-1 ring-gray-200 p-0 overflow-hidden">
                <div className="flex items-center justify-between px-6 pt-5 pb-3 border-b sticky top-0 z-10 bg-white">
                  <div className="text-gray-800 font-medium">Diff</div>
                  <div className="flex items-center gap-2">
                    <button onClick={async()=>{
                      try {
                        const a = taskVersions.find(v=>v.id===lastDiffIds.a || v.id===compareA);
                        const b = taskVersions.find(v=>v.id===lastDiffIds.b || v.id===compareB);
                        if (!a || !b || a.id===b.id) return;
                        const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                        const older = (a.versionNumber <= b.versionNumber) ? a : b;
                        const newer = (a.versionNumber <= b.versionNumber) ? b : a;
                        const latestReviser = (newer?.revisedBy || newer?.originalAuthor || username || 'User');
                        const A = older.versionNumber, B = newer.versionNumber;
                        const filename = `${toSafeName(task?.title||'Task')}_Compare-v${A}-v${B}_Inline_${yyyymmdd_hhmm()}_${toSafeName(latestReviser)}.docx`;
                        const body = { prev: (older.content||''), current: (newer.content||''), filename, authorName: latestReviser };
                        const resp = await fetch(`${base}/api/refinity/track-changes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                        if (!resp.ok) throw new Error('Export failed');
                        const blob = await resp.blob();
                        const url = window.URL.createObjectURL(blob);
                        const link = document.createElement('a');
                        link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                      } catch {}
                    }} className="px-3 py-1.5 text-sm rounded-md border border-gray-300 bg-white">Export (Inline Changes)</button>
                    <button onClick={async()=>{
                      try {
                        const a = taskVersions.find(v=>v.id===lastDiffIds.a || v.id===compareA);
                        const b = taskVersions.find(v=>v.id===lastDiffIds.b || v.id===compareB);
                        if (!a || !b || a.id===b.id) return;
                        const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                        const older = (a.versionNumber <= b.versionNumber) ? a : b;
                        const newer = (a.versionNumber <= b.versionNumber) ? b : a;
                        const latestReviser = (newer?.revisedBy || newer?.originalAuthor || username || 'User');
                        const A = older.versionNumber, B = newer.versionNumber;
                        const filename = `${toSafeName(task?.title||'Task')}_Compare-v${A}-v${B}_Comments_${yyyymmdd_hhmm()}_${toSafeName(latestReviser)}.docx`;
                        const body = { prev: (older.content||''), current: (newer.content||''), filename, authorName: latestReviser, includeComments: false };
                        const resp = await fetch(`${base}/api/refinity/track-changes-ooxml`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                        if (!resp.ok) throw new Error('Export failed');
                        const blob = await resp.blob();
                        const url = window.URL.createObjectURL(blob);
                        const link = document.createElement('a');
                        link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                      } catch {}
                    }} className="px-3 py-1.5 text-sm rounded-md border border-gray-300 bg-white">Export (Tracked Changes)</button>
                    <button onClick={async()=>{
                      try {
                        const a = taskVersions.find(v=>v.id===lastDiffIds.a || v.id===compareA);
                        const b = taskVersions.find(v=>v.id===lastDiffIds.b || v.id===compareB);
                        if (!a || !b || a.id===b.id) return;
                        const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/,'');
                        const older = (a.versionNumber <= b.versionNumber) ? a : b;
                        const newer = (a.versionNumber <= b.versionNumber) ? b : a;
                        const latestReviser = (newer?.revisedBy || newer?.originalAuthor || username || 'User');
                        const A = older.versionNumber, B = newer.versionNumber;
                        const filename = `${toSafeName(task?.title||'Task')}_Compare-v${A}-v${B}_SidebarComments_${yyyymmdd_hhmm()}_${toSafeName(latestReviser)}.docx`;
                        const body: any = {
                          prev: older.content || '',
                          current: newer.content || '',
                          filename,
                          authorName: latestReviser,
                          annotationVersionId: older.id,
                        };
                        let resp = await fetch(getApiBase('/compare-comments-with-corrections'), {
                          method: 'POST',
                          headers: { 'Content-Type': 'application/json' },
                          body: JSON.stringify(body),
                        });
                        if (!resp.ok) {
                          // Fallback to plain export of older text
                          resp = await fetch(getApiBase('/export-plain'), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ current: older.content || '', filename }),
                          });
                        }
                        if (!resp.ok) throw new Error('Export failed');
                        const blob = await resp.blob();
                        const url = window.URL.createObjectURL(blob);
                        const link = document.createElement('a');
                        link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                      } catch {}
                    }} className="px-3 py-1.5 text-sm rounded-md border border-gray-300 bg-white">Export (Sidebar Comments)</button>
                    <button onClick={()=>setCompareModalOpen(false)} className="px-3 py-1.5 text-sm rounded-md border border-gray-300 bg-white">Close</button>
                  </div>
                </div>
                <div className="px-6 pb-6 overflow-auto max-h-[70vh]">
                  <div className="prose prose-sm max-w-none text-gray-900 whitespace-pre-wrap break-words" dangerouslySetInnerHTML={{ __html: compareDiffHtml }} />
                </div>
              </div>
            </div>
          )}
        </div>
      )}

      {/* Stage: Preview full text */}
      {stage === 'preview' && (
        <>
          {!previewVersion && (
            <div className="rounded-xl ring-1 ring-gray-200 shadow-sm overflow-hidden bg-white/60 min-h-[280px] animate-pulse" />
          )}
          {previewVersion && (
            <PreviewPane
              version={previewVersion || (flowIndex >= 0 ? (taskVersions[flowIndex] || null) : null)}
              onBack={()=>setStage('flow')}
              onEdit={()=>{ if (previewVersionId) { setCurrentVersionId(previewVersionId); setStage('editor'); } }}
              compareInitially={showPreviewDiff}
            />
          )}
        </>
      )}

      {/* Stage 3: Editor */}
      {stage === 'editor' && (
        <>
          {(!currentVersion || !task?.sourceText) && (
            <div className="rounded-xl ring-1 ring-gray-200 shadow-sm overflow-hidden bg-white/60 min-h-[520px] animate-pulse" />
          )}
          {currentVersion && task?.sourceText && (
            <EditorPane
              source={task?.sourceText || ''}
              initialTranslation={currentVersion?.content || ''}
              getApiBase={getApiBase}
              onBack={()=>setStage('flow')}
              onSave={handleSaveRevision}
              onSaveEdit={editingVersionId ? ((text)=>saveEditedVersion(editingVersionId, text)) : undefined}
              taskTitle={task?.title || 'Task'}
              username={username}
              nextVersionNumber={((versions.filter(v=>v.taskId===(task?.id||'')).slice(-1)[0]?.versionNumber) || 0) + 1}
              isFullscreen={isFullscreen}
              onToggleFullscreen={()=>setIsFullscreen(v=>!v)}
              versionId={currentVersionId || ''}
            />
          )}
        </>
      )}
      </div>
    </div>
  );
};

const EditorPane: React.FC<{ source: string; initialTranslation: string; onBack: ()=>void; onSave: (text: string)=>void; onSaveEdit?: (text: string)=>void; taskTitle: string; username: string; nextVersionNumber: number; isFullscreen: boolean; onToggleFullscreen: ()=>void; versionId: string; getApiBase: (endpoint: string) => string }>=({ source, initialTranslation, onBack, onSave, onSaveEdit, taskTitle, username, nextVersionNumber, isFullscreen, onToggleFullscreen, versionId, getApiBase })=>{
  const [text, setText] = React.useState<string>(initialTranslation);
  const [saving, setSaving] = React.useState(false);
  // Sync text with incoming props when version/context changes (e.g., refresh -> data loads)
  React.useEffect(() => {
    setText(initialTranslation || '');
  }, [versionId, initialTranslation]);
  const [diffHtml, setDiffHtml] = React.useState<string>('');
  const [showDiff, setShowDiff] = React.useState<boolean>(false);
  const [revDownloadOpen, setRevDownloadOpen] = React.useState<boolean>(false);
  const [revMenuPos, setRevMenuPos] = React.useState<{ left: number; top: number } | null>(null);
  const [diffDownloadOpen, setDiffDownloadOpen] = React.useState<boolean>(false);
  const [diffMenuPos, setDiffMenuPos] = React.useState<{ left: number; top: number } | null>(null);
  const revBtnRef = React.useRef<HTMLButtonElement | null>(null);
  const diffBtnRef = React.useRef<HTMLButtonElement | null>(null);
  const sourceRef = React.useRef<HTMLDivElement>(null);
  const [textareaHeight, setTextareaHeight] = React.useState('420px');
  const [commentsOpen, setCommentsOpen] = React.useState(false);
  const [newComment, setNewComment] = React.useState('');
  const [comments, setComments] = React.useState<Array<{ id: string; text: string; ts: number }>>([]);
  const taRef = React.useRef<HTMLTextAreaElement | null>(null);
  const overlayRef = React.useRef<HTMLDivElement | null>(null);
  const wrapperRef = React.useRef<HTMLDivElement | null>(null);
  const annotatedRef = React.useRef<HTMLDivElement | null>(null);
  // Annotation layer state (revision mode only)
  const [showAnnotations, setShowAnnotations] = React.useState<boolean>(true);
  const [toastMsg, setToastMsg] = React.useState<string>('');
  const [dirty, setDirty] = React.useState<boolean>(false);
  React.useEffect(() => { setDirty(false); }, [versionId, initialTranslation]);
  React.useEffect(() => {
    const handler = (e: BeforeUnloadEvent) => {
      if (dirty) { e.preventDefault(); e.returnValue = ''; }
    };
    window.addEventListener('beforeunload', handler);
    return () => window.removeEventListener('beforeunload', handler);
  }, [dirty]);
  const showToast = (msg: string) => { setToastMsg(msg); setTimeout(()=>setToastMsg(''), 1800); };
  // Tutorial admin: choose which user's highlights to display to avoid clashes.
  const ADMIN_ANNO_VIEW_CLEAN = '__clean__';
  const ADMIN_ANNO_VIEW_EDIT = '__edit__';
  const ADMIN_ANNO_VIEW_LEGACY = '__legacy__';
  const [adminAnnoView, setAdminAnnoView] = React.useState<string>(ADMIN_ANNO_VIEW_CLEAN);
  const [annoCreators, setAnnoCreators] = React.useState<string[]>([]);
  const isTutorialMode = (() => { try { return localStorage.getItem('refinityMode') === 'tutorial'; } catch { return false; } })();
  const currentUserLower = String(username || '').toLowerCase();
  const isAdminUser = (() => {
    try { return String(JSON.parse(localStorage.getItem('user') || '{}')?.role || '').toLowerCase() === 'admin'; } catch { return false; }
  })();
  const adminCanEditAnnotations = !(isTutorialMode && isAdminUser && adminAnnoView !== ADMIN_ANNO_VIEW_EDIT);

  // Default admin view to clean on each version to avoid accidental carry-over.
  React.useEffect(() => {
    if (isTutorialMode && isAdminUser) setAdminAnnoView(ADMIN_ANNO_VIEW_CLEAN);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [versionId]);
  const ANNO_STORE_KEY = 'refinity_annotations_v1';
  type Ann = Annotation;
  const loadAnnotations = React.useCallback((): Ann[] => {
    try { const raw = localStorage.getItem(ANNO_STORE_KEY); const arr = raw ? JSON.parse(raw) : []; return Array.isArray(arr) ? arr : []; } catch { return []; }
  }, []);
  const saveAnnotations = React.useCallback((list: Ann[]) => { try { localStorage.setItem(ANNO_STORE_KEY, JSON.stringify(list)); } catch {} }, []);
  const [annotations, setAnnotations] = React.useState<Ann[]>(() => loadAnnotations());
  React.useEffect(() => { saveAnnotations(annotations); }, [annotations, saveAnnotations]);
  const versionAnnotations = React.useMemo(() => {
    const base = annotations.filter(a => a.versionId === versionId);
    if (isTutorialMode) {
      if (isAdminUser) {
        if (adminAnnoView === ADMIN_ANNO_VIEW_CLEAN) return [];
        if (adminAnnoView === ADMIN_ANNO_VIEW_EDIT) return base.filter(a => String(a.createdBy || '').toLowerCase() === currentUserLower);
        if (adminAnnoView === ADMIN_ANNO_VIEW_LEGACY) return base.filter(a => !a.createdBy);
        return base.filter(a => String(a.createdBy || '').toLowerCase() === String(adminAnnoView || '').toLowerCase());
      }
      // Student (or non-admin): be defensive and only show own (or legacy) highlights.
      return base.filter(a => !a.createdBy || String(a.createdBy || '').toLowerCase() === currentUserLower);
    }
    return base;
  }, [annotations, versionId, isTutorialMode, isAdminUser, adminAnnoView, currentUserLower]);
  const addAnnotation = (a: Ann) => setAnnotations(prev => [...prev, a]);
  const updateAnnotation = (a: Ann) => setAnnotations(prev => prev.map(x => x.id === a.id ? a : x));
  const deleteAnnotationById = (id: string) => setAnnotations(prev => prev.filter(a => a.id !== id));
  const CATEGORY_CLASS: Record<AnnotationCategory, string> = {
    distortion: 'bg-rose-100 ring-rose-200',
    omission: 'bg-amber-100 ring-amber-200',
    register: 'bg-indigo-100 ring-indigo-200',
    unidiomatic: 'bg-purple-100 ring-purple-200',
    grammar: 'bg-blue-100 ring-blue-200',
    spelling: 'bg-green-100 ring-green-200',
    punctuation: 'bg-teal-100 ring-teal-200',
    addition: 'bg-pink-100 ring-pink-200',
    other: 'bg-gray-100 ring-gray-200',
  };
  const [modalOpen, setModalOpen] = React.useState(false);
  const [editingAnn, setEditingAnn] = React.useState<Ann | null>(null);
  const [modalCategory, setModalCategory] = React.useState<AnnotationCategory>('distortion');
  const [modalComment, setModalComment] = React.useState('');
  const [modalCorrection, setModalCorrection] = React.useState('');
  const [modalIsDeletion, setModalIsDeletion] = React.useState(false);
  const [modalSelectedText, setModalSelectedText] = React.useState('');
  const [modalSourceSnippet, setModalSourceSnippet] = React.useState('');
  const [showSourcePane, setShowSourcePane] = React.useState(false);
  const [popover, setPopover] = React.useState<{ left: number; top: number; start: number; end: number } | null>(null);

  function escapeHtml(s: string): string {
    return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }
  // Load persisted annotations for this version from backend
  React.useEffect(() => {
    (async () => {
      if (!versionId) return;
      try {
        const resp = await fetch(getApiBase(`/annotations?versionId=${encodeURIComponent(versionId)}`));
        const rows = await resp.json().catch(()=>[]);
        if (Array.isArray(rows)) {
          // In tutorial mode, filter to only show annotations created by current user (unless admin)
          const isTutorial = localStorage.getItem('refinityMode') === 'tutorial';
          const user = localStorage.getItem('user');
          const currentUsername = user ? (JSON.parse(user)?.name || JSON.parse(user)?.email || '').toLowerCase() : '';
          const isAdmin = user ? (JSON.parse(user)?.role || '').toLowerCase() === 'admin' : false;
          
          let filteredRows = rows;
          if (isTutorial && !isAdmin && currentUsername) {
            // Only show annotations that either don't have createdBy (legacy) or were created by current user
            filteredRows = rows.filter((r: any) => !r.createdBy || r.createdBy.toLowerCase() === currentUsername);
          }

          // For tutorial admin, capture the list of creators for the dropdown.
          if (isTutorial && isAdmin) {
            const creators = Array.from(new Set(
              rows
                .map((r: any) => (r?.createdBy ? String(r.createdBy).toLowerCase() : ''))
                .filter(Boolean)
            )).sort();
            setAnnoCreators(creators);
          } else {
            setAnnoCreators([]);
          }
          
          setAnnotations(prev => {
            const others = prev.filter(a => a.versionId !== versionId);
            const loaded = filteredRows.map((r:any)=>{
              const existing = prev.find(a => a.id === r._id);
              return {
                id: r._id,
                versionId: r.versionId,
                start: r.start,
                end: r.end,
                category: r.category,
                comment: r.comment,
                correction: (r.correction !== undefined ? r.correction : existing?.correction),
                createdBy: (r.createdBy !== undefined ? r.createdBy : existing?.createdBy),
                createdAt: r.createdAt,
                updatedAt: r.updatedAt,
              } as Ann;
            });
            return [...others, ...loaded];
          });
        }
      } catch {}
    })();
  }, [versionId, getApiBase]);

  // Persistence helpers
  const persistCreate = React.useCallback(async (a: Ann): Promise<Ann> => {
    try {
      const body = { versionId: a.versionId, start: a.start, end: a.end, category: a.category, comment: a.comment, correction: a.correction };
      const headers: any = { 'Content-Type': 'application/json' };
      // Pass username in header for tutorial mode filtering
      if (username && username !== 'Anonymous') {
        headers['x-user-name'] = username;
      }
      const resp = await fetch(getApiBase('/annotations'), { method: 'POST', headers, body: JSON.stringify(body) });
      const row = await resp.json().catch(()=>({}));
      if (resp.ok && row && row._id) return { ...a, id: row._id };
    } catch {}
    return a;
  }, [getApiBase, username]);
  const persistUpdate = React.useCallback(async (a: Ann) => {
    try {
      const body = { start: a.start, end: a.end, category: a.category, comment: a.comment, correction: a.correction };
      await fetch(getApiBase(`/annotations/${encodeURIComponent(a.id)}`), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
    } catch {}
  }, [getApiBase]);
  const persistDelete = React.useCallback(async (id: string) => {
    try {
      await fetch(getApiBase(`/annotations/${encodeURIComponent(id)}`), { method: 'DELETE' });
    } catch {}
  }, [getApiBase]);
  function renderAnnotatedHtml(textValue: string): string {
    // Always wrap text in spans with data-start/data-end for selection offset calculation
    // even when annotations are disabled or empty
    if (!showAnnotations || !versionAnnotations.length) {
      return `<span data-start="0" data-end="${textValue.length}">${escapeHtml(textValue)}</span>`;
    }
    
    const list = versionAnnotations.slice().sort((a,b)=>a.start-b.start);
    let html = ''; let pos = 0;
    for (const a of list) {
      const start = Math.max(0, Math.min(a.start, textValue.length));
      const end = Math.max(start, Math.min(a.end, textValue.length));
      if (start > pos) {
        const plain = textValue.slice(pos, start);
        if (plain) {
          html += `<span data-start="${pos}" data-end="${start}">${escapeHtml(plain)}</span>`;
        }
      }
      const cls = CATEGORY_CLASS[a.category] || 'bg-gray-100';
      const spanText = escapeHtml(textValue.slice(start, end)) || '&nbsp;';
      html += `<span data-anno-id="${a.id}" data-start="${start}" data-end="${end}" class="inline rounded-[4px] ${cls}" title="${a.category}${a.comment ? ': '+escapeHtml(a.comment) : ''}">${spanText}</span>`;
      pos = end;
    }
    if (pos < textValue.length) {
      const tail = textValue.slice(pos);
      if (tail) {
        html += `<span data-start="${pos}" data-end="${textValue.length}">${escapeHtml(tail)}</span>`;
      }
    }
    return html;
  }

  // Helper: find paragraph snippet in source text that roughly corresponds to the
  // paragraph containing the given offset in the translation text.
  function computeSourceSnippetForOffset(sourceText: string, translationText: string, offset: number): string {
    if (!sourceText || !translationText) return '';
    
    // Find the paragraph boundaries in a text for a given position
    const findParagraph = (text: string, pos: number): { start: number; end: number } => {
      const len = text.length;
      let paraStart = 0;
      let paraEnd = len;
      
      // Look backwards from position to find paragraph start
      // Priority: double newline > single newline (if followed by capital/number/CJK)
      for (let i = Math.min(pos, len - 1); i >= 0; i--) {
        // Check for double newline (strong paragraph break)
        if (i > 0 && text[i - 1] === '\n' && text[i] === '\n') {
          paraStart = i + 1;
          break;
        }
        // Check for single newline followed by what looks like a new sentence/paragraph
        if (i > 0 && text[i - 1] === '\n') {
          // Skip whitespace after newline
          let j = i;
          while (j < len && (text[j] === ' ' || text[j] === '\t')) j++;
          if (j < len) {
            const char = text[j];
            // If it starts with capital, number, or CJK character, likely a new paragraph
            if ((char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || (char >= '\u4e00' && char <= '\u9fff')) {
              paraStart = i;
              break;
            }
          }
        }
        if (i === 0) {
          paraStart = 0;
          break;
        }
      }
      
      // Look forwards from position to find paragraph end
      for (let i = Math.max(pos, paraStart); i < len; i++) {
        // Check for double newline (strong paragraph break)
        if (i < len - 1 && text[i] === '\n' && text[i + 1] === '\n') {
          paraEnd = i;
          break;
        }
        // Check for single newline that might be a paragraph end
        if (i < len - 1 && text[i] === '\n' && text[i + 1] !== '\n') {
          // Skip whitespace after newline
          let j = i + 1;
          while (j < len && (text[j] === ' ' || text[j] === '\t')) j++;
          if (j < len) {
            const nextChar = text[j];
            // If next line starts with capital/number/CJK, this might be a paragraph end
            if ((nextChar >= 'A' && nextChar <= 'Z') || (nextChar >= '0' && nextChar <= '9') || (nextChar >= '\u4e00' && nextChar <= '\u9fff')) {
              // Make sure there's actual content before this newline
              let hasContent = false;
              for (let k = paraStart; k < i; k++) {
                if (text[k] !== ' ' && text[k] !== '\n' && text[k] !== '\t' && text[k] !== '\r') {
                  hasContent = true;
                  break;
                }
              }
              if (hasContent) {
                paraEnd = i;
                break;
              }
            }
          }
        }
        if (i === len - 1) {
          paraEnd = len;
          break;
        }
      }
      
      return { start: paraStart, end: paraEnd };
    };
    
    // Get the paragraph range in translation text that contains the offset
    const trPara = findParagraph(translationText, offset);
    
    // Find corresponding paragraph in source text
    // Use position ratio to estimate which paragraph in source corresponds
    const trRatio = trPara.start / Math.max(translationText.length, 1);
    const estimatedSrcPos = Math.floor(sourceText.length * trRatio);
    const srcPara = findParagraph(sourceText, estimatedSrcPos);
    
    // Extract the source paragraph
    const snippet = sourceText.slice(srcPara.start, srcPara.end).trim();
    
    // If snippet is too long (more than 3x the translation paragraph), try to be more selective
    if (snippet.length > (trPara.end - trPara.start) * 3 && snippet.length > 500) {
      // Try to find a more focused section - look for sentences around the estimated position
      const sentences = snippet.split(/[.!?。！？]\s+/);
      if (sentences.length > 1) {
        const midIdx = Math.floor(sentences.length / 2);
        const startIdx = Math.max(0, midIdx - 1);
        const endIdx = Math.min(sentences.length, midIdx + 2);
        return sentences.slice(startIdx, endIdx).join('. ').trim();
      }
    }
    
    return snippet || sourceText.slice(0, Math.min(500, sourceText.length)); // Fallback to first 500 chars if empty
  }
  // Adjust annotations when text changes so highlights track or are removed
  const adjustAnnotationsForEdit = React.useCallback((oldText: string, newText: string) => {
    if (oldText === newText) return;
    const oldLen = oldText.length, newLen = newText.length;
    let i = 0;
    while (i < oldLen && i < newLen && oldText[i] === newText[i]) i++;
    let a = oldLen - 1, b = newLen - 1;
    while (a >= i && b >= i && oldText[a] === newText[b]) { a--; b--; }
    const oldEditStart = i;
    const oldEditEnd = a + 1;           // [oldEditStart, oldEditEnd) replaced
    const delta = (b + 1) - (a + 1);    // change in length
    setAnnotations(prev => {
      const others = prev.filter(p => p.versionId !== versionId);
      const mine = prev.filter(p => p.versionId === versionId);
      const adjusted: Ann[] = [];
      mine.forEach(ann => {
        if (ann.end <= oldEditStart) {
          // entirely before edit region
          adjusted.push(ann);
        } else if (ann.start >= oldEditEnd) {
          // entirely after edit region → shift by delta
          adjusted.push({ ...ann, start: ann.start + delta, end: ann.end + delta });
        } else {
          // overlaps the edited region → remove (treat highlight as deleted with text)
          // do nothing (drop this annotation)
        }
      });
      return [...others, ...adjusted];
    });
  }, [versionId, setAnnotations]);

  // Map current selection within annotatedRef to plain-text offsets in `text`,
  // using data-start/data-end spans emitted by renderAnnotatedHtml for exact mapping.
  function getOffsetsFromSelection(): { start: number; end: number; rect: DOMRect | null } | null {
    const container = annotatedRef.current;
    if (!container) return null;
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0) return null;
    const range = sel.getRangeAt(0);
    if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) return null;

    const resolveOffset = (node: Node, offset: number): number | null => {
      // Find the containing span with data-start attribute
      let el: HTMLElement | null = (node.nodeType === Node.ELEMENT_NODE 
        ? node as HTMLElement 
        : node.parentElement as HTMLElement | null);
      
      while (el && el !== container && !el.hasAttribute('data-start')) {
        el = el.parentElement;
      }
      
      if (!el || !el.hasAttribute('data-start')) {
        // Fallback: try to find any span with data-start in the container
        const allSpans = container.querySelectorAll('[data-start]');
        if (allSpans.length === 0) return null;
        // Use first span as fallback
        el = allSpans[0] as HTMLElement;
      }
      
      const baseStart = Number(el.getAttribute('data-start') || '0') || 0;
      
      // Create a range from the start of this span to the target node/offset
      const r = document.createRange();
      try {
        r.selectNodeContents(el);
        r.setEnd(node, offset);
      } catch (e) {
        // If setting end fails, return baseStart as fallback
        return baseStart;
      }
      
      const localLen = r.toString().length;
      return baseStart + localLen;
    };

    const start = resolveOffset(range.startContainer, range.startOffset);
    const end = resolveOffset(range.endContainer, range.endOffset);
    
    if (start == null || end == null) {
      // Fallback: walk text nodes to calculate offset
      try {
        const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
        let charCount = 0;
        let startOffset = -1;
        let endOffset = -1;
        
        while (walker.nextNode()) {
          const node = walker.currentNode as Text;
          const nodeText = node.nodeValue || '';
          
          if (node === range.startContainer) {
            startOffset = charCount + range.startOffset;
          }
          if (node === range.endContainer) {
            endOffset = charCount + range.endOffset;
            break;
          }
          
          charCount += nodeText.length;
        }
        
        if (startOffset >= 0 && endOffset >= 0 && endOffset > startOffset) {
          const rect = range.getBoundingClientRect();
          return { start: startOffset, end: endOffset, rect };
        }
      } catch (e) {
        console.debug('[getOffsetsFromSelection] fallback failed:', e);
      }
      return null;
    }
    
    const rect = range.getBoundingClientRect();
    return { start, end, rect };
  }

  const updateSelectionPopover = React.useCallback(() => {
    const wrap = wrapperRef.current;
    const container = annotatedRef.current;
    if (!wrap || !container) { 
      setPopover(null); 
      return; 
    }
    
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0) { 
      setPopover(null); 
      return; 
    }
    
    const res = getOffsetsFromSelection();
    if (!res) { 
      setPopover(null); 
      return; 
    }
    
    const { start, end, rect } = res;
    if (!rect) { 
      setPopover(null); 
      return; 
    }
    
    if (end <= start) { 
      setPopover(null); 
      return; 
    }
    
    const wrect = wrap.getBoundingClientRect();
    const btnW = 120, btnH = 30, padding = 6;
    // Center over the selection; clamp within container
    let left = rect.left - wrect.left + (rect.width - btnW) / 2;
    left = Math.max(padding, Math.min(left, wrect.width - btnW - padding));
    // Prefer above the selection; if not enough space, place below
    let top = rect.top - wrect.top - btnH - padding;
    if (top < padding) {
      top = rect.bottom - wrect.top + padding;
    }
    
    setPopover({ left, top, start, end });
  }, []);

  React.useEffect(() => {
    if (sourceRef.current) {
      const height = sourceRef.current.offsetHeight;
      setTextareaHeight(`${height}px`);
    }
  }, [source]);
  // Re-measure when fullscreen toggles (layout changes)
  React.useEffect(() => {
    const measure = () => {
      if (sourceRef.current) {
        const h = sourceRef.current.offsetHeight;
        setTextareaHeight(`${h}px`);
      }
    };
    // double rAF to wait for layout stabilization
    requestAnimationFrame(() => requestAnimationFrame(measure));
  }, [isFullscreen]);
  // Re-measure on window resize
  React.useEffect(() => {
    const onResize = () => {
      if (sourceRef.current) {
        const h = sourceRef.current.offsetHeight;
        setTextareaHeight(`${h}px`);
      }
    };
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  // Keep overlay aligned and font-synced with textarea across renders/toggles
  React.useLayoutEffect(() => {
    const ta = taRef.current;
    const ov = overlayRef.current;
    if (!ta || !ov) return;
    // Scroll alignment
    ov.style.transform = `translateY(-${ta.scrollTop}px)`;
    // Font/layout alignment
    try {
      const cs = window.getComputedStyle(ta);
      ov.style.fontFamily = cs.fontFamily;
      ov.style.fontSize = cs.fontSize;
      ov.style.lineHeight = cs.lineHeight;
      ov.style.letterSpacing = cs.letterSpacing;
      ov.style.whiteSpace = cs.whiteSpace;
    } catch {}
  }, [showAnnotations, text, textareaHeight, isFullscreen]);

  // Build corrected text from original text and annotation corrections
  const buildCorrectedText = React.useCallback((): string => {
    const base = initialTranslation || '';
    if (!versionAnnotations.length) return base;
    const anns = versionAnnotations
      // Include deletions: we represent them as empty-string corrections.
      // (Previously we filtered out empty corrections, which caused deletions to be ignored on save.)
      .filter(a => typeof a.correction === 'string')
      .slice()
      .sort((a, b) => a.start - b.start || a.end - b.end);
    if (!anns.length) return base;
    let result = '';
    let pos = 0;
    for (const a of anns) {
      const start = Math.max(0, Math.min(a.start, base.length));
      const end = Math.max(start, Math.min(a.end, base.length));
      if (start < pos) {
        // overlapping or out-of-order; skip this annotation to avoid corrupting text
        continue;
      }
      if (pos < start) {
        result += base.slice(pos, start);
      }
      result += a.correction as string;
      pos = end;
    }
    if (pos < base.length) {
      result += base.slice(pos);
    }
    return result;
  }, [initialTranslation, versionAnnotations]);

  // Keep overlay aligned with textarea scroll position across renders/toggles
  React.useLayoutEffect(() => {
    const ta = taRef.current;
    const ov = overlayRef.current;
    if (!ta || !ov) return;
    ov.style.transform = `translateY(-${ta.scrollTop}px)`;
  }, [showAnnotations, text, textareaHeight, isFullscreen]);

  const save = async ()=>{
    setSaving(true);
    try {
      if (onSaveEdit) {
        // Editing mode: save the text as-is
        await onSaveEdit(text);
      } else {
        // Revision mode: apply all corrections from annotations before saving
        const correctedText = buildCorrectedText();
        onSave(correctedText);
      }
    } finally {
      setSaving(false);
    }
  };

  const compareNow = async ()=>{
    try {
      const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
      // Use corrected text for comparison if in revision mode (not editing mode)
      const currentText = onSaveEdit ? text : buildCorrectedText();
      const resp = await fetch(`${base}/api/refinity/diff`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prev: initialTranslation || '', current: currentText || '' }) });
      const data = await resp.json().catch(()=>({}));
      if (!resp.ok) throw new Error(data?.error || 'Diff failed');
      // Ensure layout preserved if backend HTML lacks <br/>
      const raw = String(data?.html || '');
      const ensured = raw.includes('<br') ? raw : raw.replace(/\n/g, '<br/>');
      setDiffHtml(ensured);
      setShowDiff(true);
    } catch {}
  };

  const downloadWithTrackChanges = async ()=>{
    try {
      const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
      const taskNameSafe = (taskTitle || 'Task').replace(/[^\w\-\s]/g,'').replace(/\s+/g,'_');
      const userNameSafe = toSafeName(username || 'User');
      const vNum = `v${nextVersionNumber}`;
      const filename = `${taskNameSafe}_${vNum}_Clean_${yyyymmdd_hhmm()}_${userNameSafe}.docx`;
      // Use corrected text if in revision mode
      const currentText = onSaveEdit ? text : buildCorrectedText();
      const resp = await fetch(`${base}/api/refinity/track-changes-ooxml`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prev: initialTranslation || '', current: currentText || '', filename, includeComments: false }) });
      if (!resp.ok) throw new Error('Export failed');
      const blob = await resp.blob();
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      a.remove();
      window.URL.revokeObjectURL(url);
    } catch {
      alert('Export failed');
    }
  };

  return (
    <div>
      <div className="flex items-start gap-6">
        <div className="w-1/2">
          <div className="mb-2 text-gray-700 text-sm flex items-center justify-between">
            <span>Source</span>
            <div className="flex items-center gap-2">
              {!isFullscreen && (
                <button
                  className="inline-flex items-center px-2 py-1 text-xs rounded-xl opacity-0 pointer-events-none"
                >
                  Full Screen
                </button>
              )}
            </div>
          </div>
          <div className="relative rounded-lg overflow-hidden">
            <div className="absolute inset-0 rounded-lg" style={{ background: 'radial-gradient(120%_120%_at_0%_0%, #dbeafe 0%, #ffffff 50%, #c7d2fe 100%)' }} />
            <div ref={sourceRef} className="relative rounded-lg bg-white/60 backdrop-blur-md ring-1 ring-inset ring-indigo-300 shadow p-4 min-h-[420px] whitespace-pre-wrap text-gray-900">
              {source}
            </div>
          </div>
        </div>
        <div className="w-1/2">
          <div className="mb-2 text-gray-700 text-sm flex items-center justify-between">
            <span>Translation</span>
            <div className="flex items-center gap-2">
              {isTutorialMode && isAdminUser && !onSaveEdit && (
                <div className="flex items-center gap-2">
                  <span className="text-xs text-gray-700">Highlights</span>
                  <select
                    value={adminAnnoView}
                    onChange={(e)=>setAdminAnnoView(e.target.value)}
                    className="h-8 px-2 text-sm rounded-lg border border-gray-300 bg-white"
                    title="Select which user's highlights to display (admin-only)"
                  >
                    <option value={ADMIN_ANNO_VIEW_CLEAN}>Clean (none)</option>
                    <option value={ADMIN_ANNO_VIEW_EDIT}>Edit (my highlights)</option>
                    {annoCreators.length > 0 && <option disabled>──────────</option>}
                    {annoCreators.map(u => (
                      <option key={u} value={u}>{u}</option>
                    ))}
                    <option value={ADMIN_ANNO_VIEW_LEGACY}>Legacy (unknown)</option>
                  </select>
                </div>
              )}
              {!isFullscreen && (
                <button onClick={onToggleFullscreen} className="inline-flex items-center px-2 py-1 text-xs rounded-xl bg-white/60 ring-1 ring-gray-200 text-gray-800 hover:bg-white">
                  Full Screen
                </button>
              )}
            </div>
          </div>
            <div ref={wrapperRef} className="relative overflow-hidden">
            {/* Single-surface annotated view (exact alignment) */}
            {onSaveEdit ? (
              // Editing mode: use textarea for direct editing
              <textarea
                value={text}
                onChange={(e) => {
                  setText(e.target.value);
                  setDirty(true);
                }}
                className="relative z-10 w-full px-4 py-3 border border-ui-border rounded-lg bg-white text-base leading-relaxed font-sans tracking-normal whitespace-pre-wrap overflow-y-auto resize-none"
                style={{
                  minHeight: textareaHeight,
                  height: textareaHeight,
                  maxHeight: textareaHeight,
                }}
              />
            ) : (
              // Revision mode: use annotated div
              <div
                ref={annotatedRef}
                className="relative z-10 w-full px-4 py-3 border border-ui-border rounded-lg bg-white text-base leading-relaxed font-sans tracking-normal whitespace-pre-wrap overflow-y-auto select-text"
                style={{
                  minHeight: textareaHeight,
                  height: textareaHeight,
                  maxHeight: textareaHeight,
                  userSelect: 'text',
                  WebkitUserSelect: 'text',
                }}
                onMouseDown={(e) => {
                  // Don't clear popover on mousedown - let mouseup handle it
                  // This allows selection to work properly
                }}
                onMouseUp={(e) => {
                  // Use setTimeout to ensure selection is stable
                  setTimeout(() => {
                    const sel = window.getSelection();
                    if (!sel || sel.rangeCount === 0) {
                      setPopover(null);
                      return;
                    }
                    
                    const res = getOffsetsFromSelection();
                    if (!res) { 
                      setPopover(null); 
                      return; 
                    }
                    
                    const { start, end } = res;
                    if (end - start <= 0) {
                      // collapsed caret: if inside an existing annotation, open edit modal
                      const hit = versionAnnotations.find(a => start >= a.start && start <= a.end);
                      if (hit) {
                        setEditingAnn(hit);
                        setModalCategory(hit.category);
                        setModalComment(hit.comment || '');
                        const selected = text.slice(hit.start, hit.end);
                        setModalSelectedText(selected);
                        setModalCorrection(hit.correction ?? selected);
                        // If correction is empty-string, treat it as a deletion annotation.
                        setModalIsDeletion(typeof hit.correction === 'string' && hit.correction.trim() === '');
                        setModalSourceSnippet(
                          computeSourceSnippetForOffset(source, text, hit.start)
                        );
                        setShowSourcePane(false);
                        setModalOpen(true);
                        return;
                      }
                      setPopover(null);
                      return;
                    }
                    if (!adminCanEditAnnotations) {
                      // View-only mode: allow inspecting existing highlights (handled above),
                      // but do not allow creating new highlights via selection.
                      setPopover(null);
                      return;
                    }
                    updateSelectionPopover();
                  }, 10);
                }}
                onKeyUp={()=>{
                  setTimeout(() => {
                    updateSelectionPopover();
                  }, 10);
                }}
                dangerouslySetInnerHTML={{ __html: renderAnnotatedHtml(text) }}
              />
            )}
            {/* + Comment popover */}
            {popover && (
              <div className="absolute z-20" style={{ left: popover.left, top: popover.top }}>
                <button
                  type="button"
                  onClick={()=>{
                    if (!adminCanEditAnnotations) { showToast('Switch “Highlights” to Edit (my highlights) to add.'); return; }
                    setEditingAnn(null);
                    setModalCategory('distortion');
                    setModalComment('');
                    if (popover) {
                      const sel = text.slice(popover.start, popover.end);
                      setModalSelectedText(sel);
                      setModalCorrection(sel);
                      setModalIsDeletion(false);
                      setModalSourceSnippet(
                        computeSourceSnippetForOffset(source, text, popover.start)
                      );
                    } else {
                      setModalSelectedText('');
                      setModalCorrection('');
                      setModalIsDeletion(false);
                      setModalSourceSnippet('');
                    }
                    setShowSourcePane(false);
                    setModalOpen(true);
                  }}
                  disabled={!adminCanEditAnnotations}
                  className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-3 py-1.5 text-xs font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-emerald-700 active:translate-y-0.5 transition-all duration-200"
                >
                  <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
                  <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
                  <span className="relative z-10">+ Comment</span>
                </button>
              </div>
            )}
          </div>
          <div className="mt-4 flex gap-3 relative items-center">
            <button
              onClick={() => { setDirty(false); save(); showToast('Saved'); }}
              disabled={saving}
              className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-indigo-600/70 hover:bg-indigo-700 disabled:bg-gray-400 active:translate-y-0.5 transition-all duration-200"
            >
              <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
              <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
              {saving? 'Saving…':'Save'}
            </button>
            <div className="relative inline-block align-top">
              <button
                ref={revBtnRef}
                onClick={(e)=>{
                  e.preventDefault(); e.stopPropagation();
                  const r = (e.currentTarget as HTMLElement).getBoundingClientRect();
                  const pad = 8, menuW = 224, menuH = 100;
                  let left = Math.min(Math.max(r.left, pad), window.innerWidth - menuW - pad);
                  let top = r.bottom + pad;
                  if (top + menuH > window.innerHeight - pad) top = r.top - menuH - pad;
                  setRevMenuPos({ left, top });
                  setRevDownloadOpen(o=>!o);
                }}
                className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200"
              >
                Download ▾
              </button>
              {revDownloadOpen && revMenuPos && createPortal(
                <div style={{ position: 'fixed', left: revMenuPos.left, top: revMenuPos.top, zIndex: 10000, maxHeight: '240px', overflowY: 'auto' }} className="w-56 rounded-md border border-gray-200 bg-white shadow-lg text-left">
                  <button onClick={async()=>{ setRevDownloadOpen(false); try { const base=((api.defaults as any)?.baseURL as string||'').replace(/\/$/,''); const filename=`${toSafeName(taskTitle||'Task')}_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.docx`; const currentText = onSaveEdit ? text : buildCorrectedText(); const body={ current: currentText||'', filename }; let resp=await fetch(`${base}/api/refinity/export-plain`,{ method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(body) }); if(!resp.ok) throw new Error('Export failed'); const blob=await resp.blob(); await saveBlobToDisk(blob, filename);} catch { const currentText = onSaveEdit ? text : buildCorrectedText(); await saveTextFallback(currentText||'', `${toSafeName(taskTitle||'Task')}_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.txt`);} }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Without Annotations</button>
                  <button onClick={async()=>{ setRevDownloadOpen(false); try { const base=((api.defaults as any)?.baseURL as string||'').replace(/\/$/,''); const filename=`${toSafeName(taskTitle||'Task')}_v${nextVersionNumber}_Annotated_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.docx`; const currentText = onSaveEdit ? text : buildCorrectedText(); const list=versionAnnotations.map(a=>({ start:a.start, end:a.end, category:a.category, comment:a.comment })); const body={ current: currentText||'', filename, annotations: list }; let resp=await fetch(`${base}/api/refinity/export-plain-with-annotations`,{ method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(body) }); if(!resp.ok){ resp=await fetch(`${base}/api/refinity/export-plain`,{ method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify({ current: currentText||'', filename }) }); } if(!resp.ok) throw new Error('Export failed'); const blob=await resp.blob(); await saveBlobToDisk(blob, filename);} catch { const currentText = onSaveEdit ? text : buildCorrectedText(); await saveTextFallback(currentText||'', `${toSafeName(taskTitle||'Task')}_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.txt`);} }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">With Annotations</button>
                </div>, document.body
              )}
            </div>
            {/* Comments button removed per request */}
            <button onClick={()=>{ if (!dirty || window.confirm('Discard unsaved changes?')) { setDirty(false); onBack(); } }} className="ml-auto relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200">Back</button>
          </div>
          {/* Inline comments drawer removed */}
          {showDiff && (
            <div className="mt-6">
              <div className="relative rounded-xl">
                <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-indigo-200/45 via-indigo-100/40 to-indigo-300/45" />
                <div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-4">
                  <div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
                  <div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-45" />
                  <div className="relative text-gray-900 prose prose-sm max-w-none whitespace-pre-wrap" dangerouslySetInnerHTML={{ __html: diffHtml }} />
                </div>
              </div>
              <div className="mt-4 flex justify-end">
                <div className="relative inline-block">
                  <button
                    ref={diffBtnRef}
                    onClick={(e)=>{
                      e.preventDefault(); e.stopPropagation();
                      const r = (e.currentTarget as HTMLElement).getBoundingClientRect();
                      const pad = 8, menuW = 200;
                      let left = Math.min(Math.max(r.left, pad), window.innerWidth - menuW - pad);
                      let top = r.bottom + pad;
                      if (top + 150 > window.innerHeight - pad) top = r.top - 150 - pad;
                      setDiffMenuPos({ left, top });
                      setDiffDownloadOpen(o=>!o);
                    }}
                    className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200"
                  >
                    Download ▾
                  </button>
                  {diffDownloadOpen && diffMenuPos && createPortal(
                    <div style={{ position: 'fixed', left: diffMenuPos.left, top: diffMenuPos.top, zIndex: 10000 }} className="w-52 rounded-md border border-gray-200 bg-white shadow-lg text-left">
                      <button onClick={async()=>{
                        setDiffDownloadOpen(false);
                        try {
                          const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                          const correctedText = buildCorrectedText();
                          const filename = `${toSafeName(taskTitle||'Task')}_Compare_Inline_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.docx`;
                          const body = { prev: initialTranslation||'', current: correctedText||'', filename, authorName: username||'User' };
                          let resp = await fetch(`${base}/api/refinity/track-changes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                          if (!resp.ok) {
                            resp = await fetch(`${base}/api/refinity/export-plain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current: correctedText||'', filename }) });
                          }
                          if (!resp.ok) throw new Error('Export failed');
                          const blob = await resp.blob();
                          const url = window.URL.createObjectURL(blob);
                          const link = document.createElement('a');
                          link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                        } catch {}
                      }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Inline Changes</button>
                      <button onClick={async()=>{
                        setDiffDownloadOpen(false);
                        try {
                          const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
                          const correctedText = buildCorrectedText();
                          const filename = `${toSafeName(taskTitle||'Task')}_Compare_Tracked_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.docx`;
                          const body = { prev: initialTranslation||'', current: correctedText||'', filename, authorName: username||'User', includeComments: false };
                          let resp = await fetch(`${base}/api/refinity/track-changes-ooxml`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
                          if (!resp.ok) {
                            resp = await fetch(`${base}/api/refinity/export-plain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current: correctedText||'', filename }) });
                          }
                          if (!resp.ok) throw new Error('Export failed');
                          const blob = await resp.blob();
                          const url = window.URL.createObjectURL(blob);
                          const link = document.createElement('a');
                          link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                        } catch {}
                      }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Tracked Changes</button>
                      <button onClick={async()=>{
                        setDiffDownloadOpen(false);
                        try {
                          const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/,'');
                          const correctedText = buildCorrectedText();
                          const filename = `${toSafeName(taskTitle||'Task')}_Compare_SidebarComments_${yyyymmdd_hhmm()}_${toSafeName(username||'User')}.docx`;
                          const body: any = {
                            prev: initialTranslation || '',
                            current: correctedText || '',
                            filename,
                            authorName: username || 'User',
                            annotationVersionId: versionId,
                          };
                          let resp = await fetch(getApiBase('/compare-comments-with-corrections'), {
                            method: 'POST',
                            headers: (() => {
                              const user = JSON.parse(localStorage.getItem('user') || '{}');
                              const headers: any = { 'Content-Type': 'application/json' };
                              if (user.name) headers['x-user-name'] = user.name;
                              if (user.email) headers['x-user-email'] = user.email;
                              if (user.role) {
                                headers['x-user-role'] = user.role;
                                headers['user-role'] = user.role;
                              }
                              return headers;
                            })(),
                            body: JSON.stringify(body),
                          });
                          if (!resp.ok) {
                            resp = await fetch(getApiBase('/export-plain'), {
                              method: 'POST',
                              headers: { 'Content-Type': 'application/json' },
                              body: JSON.stringify({ current: initialTranslation || '', filename }),
                            });
                          }
                          if (!resp.ok) throw new Error('Export failed');
                          const blob = await resp.blob();
                          const url = window.URL.createObjectURL(blob);
                          const link = document.createElement('a');
                          link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url);
                        } catch {}
                      }} className="block w-full text-left px-3 py-2 text-sm hover:bg-gray-50">Sidebar Comments</button>
                    </div>, document.body
                  )}
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
      {toastMsg && (
        <div className="fixed bottom-5 right-5 z-[6000] px-3 py-2 rounded-lg bg-black/80 text-white text-sm shadow-lg">{toastMsg}</div>
      )}
      {/* Annotation modal */}
      {modalOpen && (
        <div className="fixed inset-0 z-[5000] flex items-center justify-center bg-black/40">
          <motion.div
            initial={false}
            animate={{ maxWidth: showSourcePane && modalSourceSnippet ? 900 : 480 }}
            transition={{ duration: 0.2, ease: 'easeOut' }}
            className="bg-white rounded-xl shadow-xl w-full p-6"
          >
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-lg font-semibold text-gray-900">{editingAnn ? 'Edit annotation' : 'New annotation'}</h3>
              {modalSourceSnippet && (
                <button
                  type="button"
                  onClick={() => setShowSourcePane(v => !v)}
                  className="text-xs text-indigo-700 hover:text-indigo-900 underline-offset-2 hover:underline"
                >
                  {showSourcePane ? 'Hide source' : 'Show source'}
                </button>
              )}
            </div>
            <div className="space-y-4">
              <div className="md:flex md:space-x-4 space-y-4 md:space-y-0 items-start">
                <AnimatePresence initial={false}>
                  {showSourcePane && modalSourceSnippet && (
                    <motion.div
                      key="source-pane"
                      initial={{ opacity: 0, x: -10 }}
                      animate={{ opacity: 1, x: 0 }}
                      exit={{ opacity: 0, x: -10 }}
                      transition={{ duration: 0.2, ease: 'easeOut' }}
                      className="md:w-2/5 w-full space-y-1"
                    >
                      <label className="block text-sm text-gray-700">
                        Source paragraph
                      </label>
                      <div className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm text-gray-800 whitespace-pre-wrap overflow-auto max-h-64">
                        {modalSourceSnippet}
                      </div>
                    </motion.div>
                  )}
                </AnimatePresence>
                <div className="flex-1 md:w-3/5 space-y-4">
                  <div>
                    <label className="block text-sm text-gray-700 mb-1">Selected text</label>
                    <div className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm text-gray-800 whitespace-pre-wrap max-h-32 overflow-auto">
                      {modalSelectedText || '(no selection)'}
                    </div>
                  </div>
                  <div>
                    <label className="block text-sm text-gray-700 mb-1">Error type <span className="text-red-500">*</span></label>
                    <select
                      value={modalCategory}
                      onChange={(e)=>setModalCategory(e.target.value as AnnotationCategory)}
                      className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm"
                    >
                      <option value="distortion">Distortion</option>
                      <option value="omission">Unjustified omission</option>
                      <option value="register">Inappropriate register</option>
                      <option value="unidiomatic">Unidiomatic expression</option>
                      <option value="grammar">Error of grammar, syntax</option>
                      <option value="spelling">Error of spelling</option>
                      <option value="punctuation">Error of punctuation</option>
                      <option value="addition">Unjustified addition</option>
                      <option value="other">Other</option>
                    </select>
                  </div>
                  <div>
                    <div className="mb-3">
                      <label className="flex items-center gap-2 text-sm text-gray-700 cursor-pointer">
                        <input
                          type="checkbox"
                          checked={modalIsDeletion}
                          onChange={(e) => {
                            setModalIsDeletion(e.target.checked);
                            if (e.target.checked) {
                              setModalCorrection(''); // Clear correction when marking as deletion
                            }
                          }}
                          className="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500"
                        />
                        <span>Delete this text</span>
                      </label>
                    </div>
                    {!modalIsDeletion && (
                      <>
                        <label className="block text-sm text-gray-700 mb-1">Correction <span className="text-red-500">*</span></label>
                        <textarea
                          value={modalCorrection}
                          onChange={(e)=>setModalCorrection(e.target.value)}
                          rows={2}
                          className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm mb-3"
                          placeholder="Enter the corrected wording for this span…"
                        />
                      </>
                    )}
                    {modalIsDeletion && (
                      <div className="mb-3 p-3 bg-red-50 border border-red-200 rounded-md">
                        <p className="text-sm text-red-700">This text will be deleted. The selected text will be removed from the final version.</p>
                      </div>
                    )}
                    <label className="block text-sm text-gray-700 mb-1">Comment (optional)</label>
                    <textarea
                      value={modalComment}
                      onChange={(e)=>setModalComment(e.target.value)}
                      rows={3}
                      className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white text-sm"
                      placeholder="Enter the explanation or feedback…"
                    />
                  </div>
                </div>
              </div>
            </div>
            <div className="mt-6 flex items-center justify-between">
              {editingAnn && (
                <button
                  onClick={async()=>{
                    if (!adminCanEditAnnotations) { showToast('Switch “Highlights” to Edit (my highlights) to modify.'); return; }
                    await persistDelete(editingAnn.id);
                    deleteAnnotationById(editingAnn.id);
                    setModalOpen(false); setPopover(null); setEditingAnn(null); setModalIsDeletion(false);
                  }}
                  disabled={!adminCanEditAnnotations}
                  className={`px-3 py-1.5 text-sm rounded-lg text-white ${adminCanEditAnnotations ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-400 cursor-not-allowed'}`}
                >
                  Delete
                </button>
              )}
              <div className="ml-auto flex gap-2">
                <button onClick={()=>{ setModalOpen(false); setPopover(null); setEditingAnn(null); setModalIsDeletion(false); }} className="px-3 py-1.5 text-sm rounded-lg border border-gray-300 text-gray-700 bg-white hover:bg-gray-50">Cancel</button>
                <button
                  onClick={async ()=>{
                    if (!adminCanEditAnnotations) { showToast('Switch “Highlights” to Edit (my highlights) to save changes.'); return; }
                    // Allow empty correction only if deletion is checked
                    if (!modalIsDeletion && !modalCorrection.trim()) {
                      alert('Please enter a correction for this highlighted text, or check "Delete this text" to mark it for deletion.');
                      return;
                    }
                    if (editingAnn) {
                      const updated = { 
                        ...editingAnn, 
                        category: modalCategory, 
                        comment: modalComment, 
                        correction: modalIsDeletion ? '' : modalCorrection, 
                        updatedAt: Date.now() 
                      };
                      updateAnnotation(updated);
                      await persistUpdate(updated);
                    } else if (popover) {
                      const local = {
                        id:`a_${Date.now()}_${Math.random().toString(36).slice(2,7)}`,
                        versionId,
                        start: popover.start,
                        end: popover.end,
                        category: modalCategory,
                        comment: modalComment,
                        correction: modalIsDeletion ? '' : modalCorrection,
                        createdAt: Date.now(),
                        updatedAt: Date.now()
                      };
                      const saved = await persistCreate(local);
                      addAnnotation(saved);
                    }
                    setModalOpen(false); setPopover(null); setEditingAnn(null); setModalIsDeletion(false);
                  }}
                  disabled={!adminCanEditAnnotations}
                  className={`px-3 py-1.5 text-sm rounded-lg text-white ${adminCanEditAnnotations ? 'bg-indigo-600 hover:bg-indigo-700' : 'bg-gray-400 cursor-not-allowed'}`}
                >
                  Save
                </button>
              </div>
            </div>
          </motion.div>
        </div>
      )}
    </div>
  );
};

export default Refinity;

const PreviewPane: React.FC<{ version: Version | null; onBack: ()=>void; onEdit: ()=>void; compareInitially?: boolean }>=({ version, onBack, onEdit, compareInitially })=>{
  const [diffHtml, setDiffHtml] = React.useState<string>('');
  const [showDiff, setShowDiff] = React.useState<boolean>(!!compareInitially);
  React.useEffect(()=>{
    setShowDiff(!!compareInitially);
  },[compareInitially]);
  if (!version) return (
    <div>
      <div className="text-gray-700">No version selected.</div>
      <div className="mt-3">
        <button onClick={onBack} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200">Back</button>
      </div>
    </div>
  );
  const doCompare = async ()=>{
    try {
      const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
      // Compare this version with its parent if available
      const prevText = (version as any)?.parentContent || '';
      const resp = await fetch(`${base}/api/refinity/diff`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prev: prevText, current: version.content || '' }) });
      const data = await resp.json().catch(()=>({}));
      if (!resp.ok) throw new Error(data?.error || 'Diff failed');
      setDiffHtml(String(data?.html || ''));
      setShowDiff(true);
    } catch {}
  };
  return (
    <div>
      <div className="relative rounded-xl ring-1 ring-gray-200 shadow-sm overflow-hidden">
        <div className="absolute inset-0 rounded-xl bg-gradient-to-br from-orange-50 via-amber-50 to-white" />
        <div className="relative rounded-xl p-6">
          {!showDiff && (
            <div className="relative whitespace-pre-wrap text-gray-900 min-h-[220px]">{version.content}</div>
          )}
          {showDiff && (
            <div className="relative prose prose-sm max-w-none text-gray-900" dangerouslySetInnerHTML={{ __html: diffHtml }} />
          )}
        </div>
      </div>
      <div className="mt-4 flex gap-3">
        <button
          onClick={onEdit}
          className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 bg-orange-600/70 hover:bg-orange-700 active:translate-y-0.5 transition-all duration-200"
        >
          <div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
          <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
          <span className="relative z-10">Revise</span>
        </button>
        <button onClick={onBack} className="relative overflow-hidden inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-2xl text-black ring-1 ring-inset ring-white/50 backdrop-blur-md bg-white/30 active:translate-y-0.5 transition-all duration-200">Back</button>
        {/* Compare removed in revision mode per request */}
      </div>
    </div>
  );
};


