import { useEffect, useState, useRef, useCallback, useMemo } from 'react'
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import React, { Suspense } from 'react'

const EditorPanel = React.lazy(() => import('../components/editor/EditorPanel').then(m => ({ default: m.EditorPanel })))
const EditorCollaborationPanel = React.lazy(() => import('../components/editor/EditorCollaborationPanel').then(m => ({ default: m.EditorCollaborationPanel })))
const PDFPreviewPanel = React.lazy(() => import('../components/editor/PDFPreviewPanel').then(m => ({ default: m.PDFPreviewPanel })))
const NativePdfPanelShell = React.lazy(() => import('../components/editor/NativePdfPanelShell').then(m => ({ default: m.NativePdfPanelShell })))
const ConflictResolutionModal = React.lazy(() => import('../components/ConflictResolutionModal').then(m => ({ default: m.ConflictResolutionModal })))
const ReasoningHistoryModal = React.lazy(() => import('../components/ReasoningHistoryModal').then(m => ({ default: m.ReasoningHistoryModal })))
import { useEditorStore } from '../stores/editorStore'
import { familiarosClient } from '../services/familiaros-client'
import type {
  CollaborationDocumentConflictResolution,
  CorpusCompileTaskPayload,
  CorpusIntegrityReportResponse,
  CorpusIntegritySummary,
  CorpusEntity,
  CorpusLocalHistoryTaskPayload,
  CorpusMigrationPortabilityTaskPayload,
  CorpusUvRuntimeTaskPayload,
} from '../services/familiaros-client'
import { notifications } from '../services/notifications'
import { subscribeScriptoriumCommand } from '../services/command-bus'
import { recordUxTelemetryEvent } from '../services/ux-telemetry'
const MemoryInspector = React.lazy(() => import('../components/memory/MemoryInspector').then(m => ({ default: m.MemoryInspector })))
import { Activity, FileText, GripVertical, Network, Pin, PinOff } from 'lucide-react'
const DocumentOutlinePanel = React.lazy(() => import('../components/editor/DocumentOutlinePanel').then(m => ({ default: m.DocumentOutlinePanel })))
const ChatPanel = React.lazy(() => import('../components/editor/ChatPanel').then(m => ({ default: m.ChatPanel })))

import type { FileNode } from '../components/FileTree'
const FileTree = React.lazy(() => import('../components/FileTree').then(m => ({ default: m.FileTree })))
import {
  buildEntityPinnedMetadata,
  derivePinnedEntityIds,
  isEntityPinned,
  loadPinnedEntityIdsFromStorage,
  persistPinnedEntityIdsToStorage,
} from '../components/editor/context-scope-control'
import {
  type EditorRightPaneTab,
  type EditorWorkspaceContext,
  loadEditorWorkspaceState,
  persistEditorWorkspaceState,
} from '../utils/editor-workspace'
import {
  buildNativePdfBytesCacheKey,
  nativePdfDocumentStore,
} from '../services/native-pdf-document-store'
import {
  buildFallbackSyncTexEntries,
  resolveCompileArtifactContext,
  type CompileArtifactContext,
} from '../utils/compile-artifact-routing'
import {
  buildCrossDocumentCompareFilterPresets,
  buildCrossDocumentCompareSummary,
  buildCrossDocumentCompareLineRows,
  buildCrossDocumentCompareIssueSummary,
  filterCrossDocumentCompareLineRows,
  flattenFileTreeToCompareCandidates,
  resolveAdjacentCompareIssueLine,
  resolveCompareIssueContextAtLine,
  type CrossDocumentCompareFilterPreset,
  type CrossDocumentCompareLineRow,
  type CrossDocumentCompareLineFilterMode,
  type CrossDocumentCompareIssueKind,
  type CrossDocumentCompareCandidate,
} from '../utils/cross-document-compare'
import {
  buildEditorCompareContextParams,
  buildEditorFocusedIssueContextParams,
  buildEditorFocusedIssueRemediationParams,
  buildEditorIssueBucketContextParams,
  buildEditorIssueBucketRemediationParams,
  deriveEditorCompareIssueFilterMode,
  parseEditorCompareRouteContext,
  resolveCompareIssueRemediationRoute,
  resolveCompareCandidateFromRouteContext,
} from '../utils/editor-compare-route-context'
import { buildCollaborationDocumentOperationEnvelope } from '../utils/collaboration-document-ops'
import {
  clearPendingCollaborationSync,
  listPendingCollaborationSyncs,
  writePendingCollaborationSync,
  type PendingCollaborationDocumentSync,
} from '../utils/collaboration-pending-sync'

const COMPARE_ISSUE_KINDS: CrossDocumentCompareIssueKind[] = ['different', 'primary_only', 'secondary_only']

function compareIssueKindLabel(kind: CrossDocumentCompareIssueKind) {
  if (kind === 'different') return 'Changed lines'
  if (kind === 'primary_only') return 'Draft-only lines'
  return 'Compare-only lines'
}

function compareIssueKindFilterMode(kind: CrossDocumentCompareIssueKind): CrossDocumentCompareLineFilterMode {
  return kind === 'different' ? 'different' : 'one_sided'
}

export function buildEditorVideoStudioHandoffState(
  searchParams: Pick<URLSearchParams, 'get'>,
  routeProjectId?: string | null,
) {
  const source = String(searchParams.get('source') || '').trim()
  const active = source === 'video_studio'
  const preflightId = active ? String(searchParams.get('preflight_id') || '').trim() : ''
  const projectId = active ? String(routeProjectId || '').trim() : ''

  return {
    active,
    projectId,
    preflightId,
    videoStudioHref: '/app/remotion',
    note: active
      ? `Video Studio handoff loaded${preflightId ? ` from preflight ${preflightId}` : ''}. Continue refining the workspace here or jump back to Video Studio before execution.`
      : '',
  }
}

export function buildEditorDiagramStudioHandoffState(
  searchParams: Pick<URLSearchParams, 'get'>,
  routeProjectId?: string | null,
) {
  const source = String(searchParams.get('source') || '').trim()
  const active = source === 'diagram_studio'
  const projectId = active ? String(routeProjectId || '').trim() : ''
  const runId = active ? String(searchParams.get('run_id') || '').trim() : ''
  const artifactId = active ? String(searchParams.get('artifact_id') || '').trim() : ''
  const sceneId = active ? String(searchParams.get('scene_id') || '').trim() : ''
  const nextAiDrawIoHref = '/app/nextaidrawio'
  const reviewParams = new URLSearchParams({ source: 'diagram_studio' })
  if (projectId) reviewParams.set('project_id', projectId)
  if (sceneId) reviewParams.set('scene_id', sceneId)

  return {
    active,
    projectId,
    runId,
    artifactId,
    sceneId,
    nextAiDrawIoHref,
    reviewHref: `/app/excalidraw?${reviewParams.toString()}`,
    note: active
      ? `AI Diagram Studio handoff loaded${sceneId ? ` for scene ${sceneId}` : ''}${runId ? ` from run ${runId}` : ''}. Continue refining the linked workspace here or jump back to AI Diagram Studio for publish and review controls.`
      : '',
  }
}

export function buildEditorMindMapHandoffState(
  searchParams: Pick<URLSearchParams, 'get'>,
  routeProjectId?: string | null,
) {
  const source = String(searchParams.get('source') || '').trim()
  const active = source === 'mindmap'
  const projectId = active ? String(routeProjectId || '').trim() : ''
  const sceneId = active ? String(searchParams.get('mindmap_scene_id') || '').trim() : ''
  const nodeId = active ? String(searchParams.get('mindmap_node_id') || '').trim() : ''
  const topic = active ? String(searchParams.get('mindmap_topic') || '').trim() : ''
  const entityType = active ? String(searchParams.get('mindmap_entity_type') || '').trim() : ''
  const entityId = active ? String(searchParams.get('mindmap_entity_id') || '').trim() : ''

  return {
    active,
    projectId,
    sceneId,
    nodeId,
    topic,
    entityType,
    entityId,
    mindMapHref: '/app/mindmap',
    note: active
      ? `MindMap handoff loaded${topic ? ` for topic ${topic}` : ''}${nodeId ? ` from node ${nodeId}` : ''}. Continue editing the linked project here or return to the knowledge map for surrounding corpus context.`
      : '',
  }
}

type CompareProposalReviewDecision = 'accept' | 'reject'

function buildDraftFromCompareProposalReview(
  rows: CrossDocumentCompareLineRow[],
  decisions: Record<number, CompareProposalReviewDecision>
) {
  const nextLines: string[] = []
  for (const row of rows) {
    const decision = decisions[row.lineNumber]
    const useSecondary = decision === 'accept'
    if (useSecondary) {
      if (row.status === 'primary_only') continue
      nextLines.push(row.secondaryText || '')
      continue
    }
    if (row.status === 'secondary_only') continue
    nextLines.push(row.primaryText || '')
  }
  return nextLines.join('\n')
}

type CompareIntegrityIssueField = keyof Pick<
  CorpusIntegritySummary,
  | 'relation_violations'
  | 'citation_link_mismatches'
  | 'duplicate_document_labels'
  | 'ambiguous_reference_labels'
  | 'unresolved_document_references'
  | 'missing_entity_links'
  | 'citation_entities_missing_keys'
  | 'citation_links_missing_keys'
  | 'citation_bibliography_missing'
  | 'unknown_document_citations'
  | 'unknown_bibliography_entries'
  | 'orphan_bibliography_entries'
>

type CompareIntegrityAttachmentSpec = {
  key: CompareIntegrityIssueField
  label: string
}

const COMPARE_ISSUE_INTEGRITY_ATTACHMENT_MAP: Record<CrossDocumentCompareIssueKind, CompareIntegrityAttachmentSpec[]> = {
  different: [
    { key: 'relation_violations', label: 'Relation Violations' },
    { key: 'citation_link_mismatches', label: 'Citation Link Mismatches' },
    { key: 'duplicate_document_labels', label: 'Duplicate Document Labels' },
    { key: 'ambiguous_reference_labels', label: 'Ambiguous Reference Labels' },
  ],
  primary_only: [
    { key: 'unresolved_document_references', label: 'Unresolved Document References' },
    { key: 'missing_entity_links', label: 'Missing Entity Links' },
    { key: 'citation_entities_missing_keys', label: 'Citation Entities Missing Keys' },
    { key: 'citation_links_missing_keys', label: 'Citation Links Missing Keys' },
    { key: 'citation_bibliography_missing', label: 'Citation Bibliography Missing' },
  ],
  secondary_only: [
    { key: 'unknown_document_citations', label: 'Unknown Document Citations' },
    { key: 'citation_entities_missing_keys', label: 'Citation Entities Missing Keys' },
    { key: 'citation_links_missing_keys', label: 'Citation Links Missing Keys' },
    { key: 'unknown_bibliography_entries', label: 'Unknown Bibliography Entries' },
    { key: 'orphan_bibliography_entries', label: 'Orphan Bibliography Entries' },
  ],
}

type UvRuntimePreferences = {
  projectPath: string
  pythonVersion: string
}

const DEFAULT_UV_RUNTIME_PREFERENCES: UvRuntimePreferences = {
  projectPath: '',
  pythonVersion: '3.11',
}

function loadUvRuntimePreferences(storageKey: string): UvRuntimePreferences {
  try {
    const raw = window.localStorage.getItem(storageKey)
    if (!raw) return DEFAULT_UV_RUNTIME_PREFERENCES
    const parsed = JSON.parse(raw) as Partial<UvRuntimePreferences>
    return {
      projectPath: typeof parsed?.projectPath === 'string' ? parsed.projectPath : DEFAULT_UV_RUNTIME_PREFERENCES.projectPath,
      pythonVersion:
        typeof parsed?.pythonVersion === 'string' && parsed.pythonVersion.trim()
          ? parsed.pythonVersion.trim()
          : DEFAULT_UV_RUNTIME_PREFERENCES.pythonVersion,
    }
  } catch {
    return DEFAULT_UV_RUNTIME_PREFERENCES
  }
}

function persistUvRuntimePreferences(storageKey: string, preferences: UvRuntimePreferences) {
  try {
    window.localStorage.setItem(storageKey, JSON.stringify(preferences))
  } catch {
    // No-op when persistence is unavailable in constrained/browser-private contexts.
  }
}

export function EditorSimplified() {
  const { projectId: routeProjectId } = useParams()
  const navigate = useNavigate()
  const [searchParams] = useSearchParams()
  const zenMode = useEditorStore((state) => state.zenMode)
  const toggleZenMode = useEditorStore((state) => state.toggleZenMode)

  // Zen Mode Global Shortcut Cmd/Ctrl+J
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'j') {
        e.preventDefault()
        toggleZenMode()
      }
    }
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [toggleZenMode])

  useEffect(() => {
    const handleZenToggleRequest = () => toggleZenMode()
    window.addEventListener('familiaros:editor:toggle-zen', handleZenToggleRequest)
    return () => window.removeEventListener('familiaros:editor:toggle-zen', handleZenToggleRequest)
  }, [toggleZenMode])

  useEffect(() => {
    const handleExplorerShortcut = (event: KeyboardEvent) => {
      const target = event.target as HTMLElement | null
      const tagName = target?.tagName?.toLowerCase()
      const isTypingTarget = tagName === 'input' || tagName === 'textarea' || tagName === 'select' || target?.isContentEditable
      if (isTypingTarget) return
      if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === 'e') {
        event.preventDefault()
        setIsExplorerVisible((current) => !current)
      }
    }
    window.addEventListener('keydown', handleExplorerShortcut)
    return () => window.removeEventListener('keydown', handleExplorerShortcut)
  }, [])

  const compareRouteContext = useMemo(() => parseEditorCompareRouteContext(searchParams), [searchParams])
  const editorVideoStudioHandoff = useMemo(
    () => buildEditorVideoStudioHandoffState(searchParams, routeProjectId || null),
    [routeProjectId, searchParams],
  )
  const editorDiagramStudioHandoff = useMemo(
    () => buildEditorDiagramStudioHandoffState(searchParams, routeProjectId || null),
    [routeProjectId, searchParams],
  )
  const editorMindMapHandoff = useMemo(
    () => buildEditorMindMapHandoffState(searchParams, routeProjectId || null),
    [routeProjectId, searchParams],
  )
  const {
    content,
    updateContent,
    setProject,
    setLastSyncedContent,
    setDocVersion: setStoredDocVersion,
  } = useEditorStore()
  const protectedRegionsStorageKey = useMemo(
    () => `familiaros:editor:protected-regions:${String(routeProjectId || 'default').trim() || 'default'}`,
    [routeProjectId]
  )
  
  const [docId, setDocId] = useState<string | null>(null)
  const [docVersion, setDocVersion] = useState<number | undefined>(undefined)
  const [loadingState, setLoadingState] = useState<'loading' | 'error' | 'success'>('loading')
  const [errorMessage, setErrorMessage] = useState<string | null>(null)
  const [isAuthenticated, setIsAuthenticated] = useState(false)
  const [isAuthChecked, setIsAuthChecked] = useState(false)
  const [collaborationActorId, setCollaborationActorId] = useState('unified-user')
  const [collaborationActorLabel, setCollaborationActorLabel] = useState('operator@familiar-os.com')
  const [isBrowserOnline, setIsBrowserOnline] = useState(() => typeof navigator === 'undefined' ? true : navigator.onLine !== false)
  const [pendingDocumentSync, setPendingDocumentSync] = useState<PendingCollaborationDocumentSync | null>(null)
  const [pendingDocumentSyncCount, setPendingDocumentSyncCount] = useState(0)
  
  const [isSaving, setIsSaving] = useState(false)
  const [isRetryingPendingDocumentSync, setIsRetryingPendingDocumentSync] = useState(false)
  const [showConflictModal, setShowConflictModal] = useState(false)
  const [conflictMessage, setConflictMessage] = useState<string | undefined>(undefined)
  const [conflictResolution, setConflictResolution] = useState<CollaborationDocumentConflictResolution | null>(null)
  const [isApplyingConflictResolution, setIsApplyingConflictResolution] = useState(false)
  const [isReasoningHistoryOpen, setIsReasoningHistoryOpen] = useState(false)
  const [isCompiling, setIsCompiling] = useState(false)
  const [pdfUrl, setPdfUrl] = useState<string | null>(null)
  const [compileArtifactContext, setCompileArtifactContext] = useState<CompileArtifactContext | null>(null)
  const [compileError, setCompileError] = useState<string | null>(null)
  const [isNativePdfLoading, setIsNativePdfLoading] = useState(false)
  const [nativePdfRuntimeError, setNativePdfRuntimeError] = useState<string | null>(null)
  const [nativePdfPageCount, setNativePdfPageCount] = useState<number | null>(null)
  const [nativePdfByteLength, setNativePdfByteLength] = useState<number | null>(null)
  const [editorFocusRequest, setEditorFocusRequest] = useState<{ line: number; column?: number; nonce: number } | null>(null)
  const [lastSaved, setLastSaved] = useState<Date | null>(null)
  const [projectName, setProjectName] = useState<string>('Untitled Document')
  const [selectionSnapshot, setSelectionSnapshot] = useState<{
    startLine: number
    endLine: number
    startColumn: number
    endColumn: number
    selectedText: string
  } | null>(null)
  const [protectedRegions, setProtectedRegions] = useState<Array<{
    id: string
    startLine: number
    endLine: number
    label: string
    createdAt: string
  }>>([])
  
  const initialWorkspaceState = useMemo(() => loadEditorWorkspaceState(), [])
  const [splitRatio, setSplitRatio] = useState(initialWorkspaceState.centerPaneRatio)
  const [isPreviewVisible, setIsPreviewVisible] = useState(initialWorkspaceState.rightPaneVisible)
  const [isChatVisible, setIsChatVisible] = useState(false)
  const [chatRatio, setChatRatio] = useState(20)
  const [isExplorerVisible, setIsExplorerVisible] = useState(initialWorkspaceState.leftPaneVisible)
  const [leftPaneWidthPx, setLeftPaneWidthPx] = useState(initialWorkspaceState.leftPaneWidthPx)
  const [activeRightPaneTab, setActiveRightPaneTab] = useState<EditorRightPaneTab>(initialWorkspaceState.activeRightPaneTab)
  const [selectedContext, setSelectedContext] = useState<EditorWorkspaceContext>(initialWorkspaceState.selectedContext)
  const [contextFilter, setContextFilter] = useState(initialWorkspaceState.contextFilter)
  const [leftPaneTab, setLeftPaneTab] = useState<'files' | 'explorer' | 'outline'>('files')

  const rightPaneTitle = useMemo(() => {
    if (activeRightPaneTab === 'preview') return 'PDF Preview'
    if (activeRightPaneTab === 'native_pdf') return 'Native PDF'
    if (activeRightPaneTab === 'memory') return 'AI Memory'
    if (activeRightPaneTab === 'collaboration') return 'Collaboration'
    return 'Insights'
  }, [activeRightPaneTab])
  const [dragTarget, setDragTarget] = useState<'left' | 'center' | null>(null)

  // G2 — Corpus context scope control: pinned entities + browseable entity list
  const pinnedEntitiesStorageKey = useMemo(
    () => `familiaros:editor:pinned-entities:${String(routeProjectId || 'default').trim() || 'default'}`,
    [routeProjectId]
  )
  const [pinnedEntityIds, setPinnedEntityIds] = useState<string[]>(() => loadPinnedEntityIdsFromStorage(pinnedEntitiesStorageKey))
  const [contextEntities, setContextEntities] = useState<CorpusEntity[]>([])
  const [isLoadingContextEntities, setIsLoadingContextEntities] = useState(false)
  const [contextEntityFilter, setContextEntityFilter] = useState('')
  const [contextEntityBrowseOpen, setContextEntityBrowseOpen] = useState(false)
  const [contextEntityPinSyncId, setContextEntityPinSyncId] = useState<string | null>(null)
  const [projectFiles, setProjectFiles] = useState<FileNode[]>([])
  const [activeProjectFileId, setActiveProjectFileId] = useState<string | null>(null)
  const [isRefreshingProjectFiles, setIsRefreshingProjectFiles] = useState(false)
  const [compareCandidates, setCompareCandidates] = useState<CrossDocumentCompareCandidate[]>([])
  const [compareDocumentId, setCompareDocumentId] = useState('')
  const [compareDocument, setCompareDocument] = useState<any | null>(null)
  const [compareDocumentName, setCompareDocumentName] = useState<string | null>(null)
  const [isLoadingCompareDocument, setIsLoadingCompareDocument] = useState(false)
  const [compareDocumentError, setCompareDocumentError] = useState<string | null>(null)
  const [compareLineFilter, setCompareLineFilter] = useState<CrossDocumentCompareLineFilterMode>(
    () => deriveEditorCompareIssueFilterMode(compareRouteContext)
  )
  const [compareActiveIssueLine, setCompareActiveIssueLine] = useState<number | null>(null)
  const [compareIntegritySummary, setCompareIntegritySummary] = useState<CorpusIntegrityReportResponse['summary'] | null>(null)
  const [compareIntegrityIssues, setCompareIntegrityIssues] = useState<CorpusIntegrityReportResponse['issues'] | null>(null)
  const [compareIntegrityGeneratedAt, setCompareIntegrityGeneratedAt] = useState<string | null>(null)
  const [isLoadingCompareIntegrity, setIsLoadingCompareIntegrity] = useState(false)
  const [compareIntegrityError, setCompareIntegrityError] = useState<string | null>(null)
  const [compareProposalReviewDecisions, setCompareProposalReviewDecisions] = useState<Record<number, CompareProposalReviewDecision>>({})
  const [compareProposalReviewAssignments, setCompareProposalReviewAssignments] = useState<Record<number, string>>({})
  const [compareProposalReviewDefaultAssignee, setCompareProposalReviewDefaultAssignee] = useState('')
  const [compareProposalReviewLastAppliedAt, setCompareProposalReviewLastAppliedAt] = useState<string | null>(null)
  const [corpusCompileMode, setCorpusCompileMode] = useState<CorpusCompileTaskPayload['mode']>('batch')
  const [corpusCompileProfile, setCorpusCompileProfile] = useState('default')
  const [corpusCompileVolumeId, setCorpusCompileVolumeId] = useState('')
  const [corpusCompileLabel, setCorpusCompileLabel] = useState('')
  const [corpusCompileDocumentIds, setCorpusCompileDocumentIds] = useState('')
  const [corpusCompileForce, setCorpusCompileForce] = useState(false)
  const [corpusCompileTaskId, setCorpusCompileTaskId] = useState<string | null>(null)
  const [corpusCompileRunId, setCorpusCompileRunId] = useState<string | null>(null)
  const [corpusCompileState, setCorpusCompileState] = useState<'idle' | 'enqueuing' | 'running' | 'success' | 'error'>('idle')
  const [corpusCompileResult, setCorpusCompileResult] = useState<any>(null)
  const [corpusCompileError, setCorpusCompileError] = useState<string | null>(null)
  const [corpusCompileLastRunAt, setCorpusCompileLastRunAt] = useState<string | null>(null)
  const [uvRuntimeAction, setUvRuntimeAction] = useState<CorpusUvRuntimeTaskPayload['action']>('status')
  const [uvRuntimePythonVersion, setUvRuntimePythonVersion] = useState('3.11')
  const [uvRuntimeProjectPath, setUvRuntimeProjectPath] = useState('')
  const [uvRuntimeTaskId, setUvRuntimeTaskId] = useState<string | null>(null)
  const [uvRuntimeState, setUvRuntimeState] = useState<'idle' | 'enqueuing' | 'running' | 'success' | 'error'>('idle')
  const [uvRuntimeResult, setUvRuntimeResult] = useState<any>(null)
  const [uvRuntimeError, setUvRuntimeError] = useState<string | null>(null)
  const [uvRuntimeLastRunAt, setUvRuntimeLastRunAt] = useState<string | null>(null)
  const [uvPackagesInput, setUvPackagesInput] = useState('')
  const [uvRequirementsFile, setUvRequirementsFile] = useState('requirements.txt')
  // Scientific workflow fields
  const [uvWorkflowCommand, setUvWorkflowCommand] = useState('')
  const [uvWorkflowArgs, setUvWorkflowArgs] = useState('')
  const [uvWorkflowId, setUvWorkflowId] = useState('default')
  const [uvWorkflowCheckpointName, setUvWorkflowCheckpointName] = useState('')
  const [uvWorkflowStepIndex, setUvWorkflowStepIndex] = useState('0')
  const [uvWorkflowCheckpointId, setUvWorkflowCheckpointId] = useState('')
  const [localHistoryAction, setLocalHistoryAction] = useState<CorpusLocalHistoryTaskPayload['action']>('list')
  const [localHistoryLabel, setLocalHistoryLabel] = useState('')
  const [localHistorySnapshotId, setLocalHistorySnapshotId] = useState('')
  const [localHistoryLeftSnapshotId, setLocalHistoryLeftSnapshotId] = useState('')
  const [localHistoryRightSnapshotId, setLocalHistoryRightSnapshotId] = useState('')
  const [localHistoryMaxEntries, setLocalHistoryMaxEntries] = useState('20')
  const [localHistoryTaskId, setLocalHistoryTaskId] = useState<string | null>(null)
  const [localHistoryState, setLocalHistoryState] = useState<'idle' | 'enqueuing' | 'running' | 'success' | 'error'>('idle')
  const [localHistoryResult, setLocalHistoryResult] = useState<any>(null)
  const [localHistoryError, setLocalHistoryError] = useState<string | null>(null)
  const [localHistoryLastRunAt, setLocalHistoryLastRunAt] = useState<string | null>(null)
  const [localHistoryRestorePreview, setLocalHistoryRestorePreview] = useState<any>(null)
  const [localHistoryRestoreConfirmPending, setLocalHistoryRestoreConfirmPending] = useState(false)
  const [migrationPortabilityAction, setMigrationPortabilityAction] = useState<CorpusMigrationPortabilityTaskPayload['action']>('export_snapshot')
  const [migrationPortabilityLabel, setMigrationPortabilityLabel] = useState('baseline')
  const [migrationPortabilitySnapshotInput, setMigrationPortabilitySnapshotInput] = useState('')
  const [migrationPortabilityTaskId, setMigrationPortabilityTaskId] = useState<string | null>(null)
  const [migrationPortabilityState, setMigrationPortabilityState] = useState<'idle' | 'enqueuing' | 'running' | 'success' | 'error'>('idle')
  const [migrationPortabilityResult, setMigrationPortabilityResult] = useState<any>(null)
  const [migrationPortabilityError, setMigrationPortabilityError] = useState<string | null>(null)
  const [migrationPortabilityLastRunAt, setMigrationPortabilityLastRunAt] = useState<string | null>(null)
  const uvRuntimeStorageKey = useMemo(
    () => `familiaros:editor:uv-runtime:${String(routeProjectId || 'default').trim() || 'default'}`,
    [routeProjectId]
  )
  const fileUploadInputId = useMemo(
    () => `editor-simplified-file-upload-${String(routeProjectId || 'default').trim() || 'default'}`,
    [routeProjectId]
  )
  const containerRef = useRef<HTMLDivElement>(null)
  const comparePrimaryPreviewRef = useRef<HTMLDivElement>(null)
  const compareSecondaryPreviewRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const preferences = loadUvRuntimePreferences(uvRuntimeStorageKey)
    setUvRuntimeProjectPath(preferences.projectPath)
    setUvRuntimePythonVersion(preferences.pythonVersion)
  }, [uvRuntimeStorageKey])

  useEffect(() => {
    persistUvRuntimePreferences(uvRuntimeStorageKey, {
      projectPath: uvRuntimeProjectPath,
      pythonVersion: uvRuntimePythonVersion,
    })
  }, [uvRuntimeStorageKey, uvRuntimeProjectPath, uvRuntimePythonVersion])
  const compareRouteDocSelectionAppliedRef = useRef<string | null>(null)
  const compareRouteFilterAppliedRef = useRef<string | null>(null)
  const compareRouteAutoLoadAttemptedRef = useRef<string | null>(null)
  const compareRouteAutoIntegrityLoadAttemptedRef = useRef<string | null>(null)
  const compareRouteAutoScrollAttemptedRef = useRef<string | null>(null)

  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(protectedRegionsStorageKey)
      if (!raw) {
        setProtectedRegions([])
        return
      }
      const parsed = JSON.parse(raw)
      setProtectedRegions(Array.isArray(parsed) ? parsed : [])
    } catch {
      setProtectedRegions([])
    }
  }, [protectedRegionsStorageKey])

  useEffect(() => {
    try {
      window.localStorage.setItem(protectedRegionsStorageKey, JSON.stringify(protectedRegions))
    } catch {
      // ignore persistence failures
    }
  }, [protectedRegions, protectedRegionsStorageKey])

  const refreshPendingDocumentSyncState = useCallback((options?: {
    projectId?: string | null
    documentId?: string | null
  }) => {
    const projectId = options?.projectId ?? routeProjectId
    const documentId = options?.documentId ?? docId
    if (!projectId) {
      setPendingDocumentSync(null)
      setPendingDocumentSyncCount(0)
      return
    }
    const records = listPendingCollaborationSyncs(window.localStorage, projectId)
    setPendingDocumentSyncCount(records.length)
    if (!documentId) {
      setPendingDocumentSync(null)
      return
    }
    setPendingDocumentSync(records.find((record) => record.documentId === documentId) || null)
  }, [docId, routeProjectId])

  useEffect(() => {
    refreshPendingDocumentSyncState()
  }, [refreshPendingDocumentSyncState])

  const clearPendingDocumentSync = useCallback((targetDocumentId?: string | null) => {
    const effectiveProjectId = routeProjectId
    const effectiveDocumentId = targetDocumentId ?? docId
    if (!effectiveProjectId || !effectiveDocumentId) return
    clearPendingCollaborationSync(window.localStorage, effectiveProjectId, effectiveDocumentId)
    refreshPendingDocumentSyncState({
      projectId: effectiveProjectId,
      documentId: docId,
    })
  }, [docId, refreshPendingDocumentSyncState, routeProjectId])

  const queuePendingDocumentSync = useCallback(({
    baseContent,
    nextContent,
    version,
    errorMessage,
  }: {
    baseContent: string
    nextContent: string
    version: number
    errorMessage: string | null
  }) => {
    if (!routeProjectId || !docId) return null
    const nowIso = new Date().toISOString()
    const queuedRecord: PendingCollaborationDocumentSync = {
      projectId: routeProjectId,
      documentId: docId,
      actorId: collaborationActorId,
      baseContent,
      nextContent,
      version,
      queuedAt: pendingDocumentSync?.queuedAt || nowIso,
      updatedAt: nowIso,
      lastError: errorMessage,
    }
    writePendingCollaborationSync(window.localStorage, queuedRecord)
    refreshPendingDocumentSyncState({
      projectId: routeProjectId,
      documentId: docId,
    })
    return queuedRecord
  }, [collaborationActorId, docId, pendingDocumentSync?.queuedAt, refreshPendingDocumentSyncState, routeProjectId])

  const handleInsertProtectedRegion = useCallback(() => {
    if (!selectionSnapshot?.selectedText?.trim()) {
      notifications.error('Select some LaTeX content first to protect it.')
      return
    }

    const label = `region-${protectedRegions.length + 1}`
    const wrapped = [
      `% @protected-region:start ${label}`,
      selectionSnapshot.selectedText,
      `% @protected-region:end ${label}`,
    ].join('\n')

    if (!content.includes(selectionSnapshot.selectedText)) {
      notifications.error('Could not resolve the current selection in document content.')
      return
    }

    updateContent(content.replace(selectionSnapshot.selectedText, wrapped))
    setProtectedRegions((prev) => [
      ...prev,
      {
        id: label,
        startLine: selectionSnapshot.startLine,
        endLine: selectionSnapshot.endLine + 2,
        label,
        createdAt: new Date().toISOString(),
      },
    ])
    notifications.success(`Protected region "${label}" added.`)
  }, [content, protectedRegions.length, selectionSnapshot, updateContent])

  // G2 — Persist pinned entity IDs
  useEffect(() => {
    try {
      persistPinnedEntityIdsToStorage(pinnedEntitiesStorageKey, pinnedEntityIds)
    } catch {
      // ignore storage failures
    }
  }, [pinnedEntityIds, pinnedEntitiesStorageKey])

  useEffect(() => {
    setPinnedEntityIds(loadPinnedEntityIdsFromStorage(pinnedEntitiesStorageKey))
  }, [pinnedEntitiesStorageKey])

  // G2 — Load corpus entities when the context (explorer) pane is active
  const handleLoadContextEntities = useCallback(async () => {
    setIsLoadingContextEntities(true)
    try {
      const response = await familiarosClient.listCorpusEntities({ limit: 30 })
      setContextEntities(response.entities || [])
      const metadataPinnedIds = derivePinnedEntityIds(response.entities || [])
      if (metadataPinnedIds.length) {
        setPinnedEntityIds((current) => Array.from(new Set([...current, ...metadataPinnedIds])))
      }
    } catch {
      setContextEntities([])
    } finally {
      setIsLoadingContextEntities(false)
    }
  }, [])

  useEffect(() => {
    const shouldLoadContextEntities =
      (isExplorerVisible && leftPaneTab === 'explorer') ||
      (isPreviewVisible && activeRightPaneTab === 'insights' && contextEntityBrowseOpen)

    if (shouldLoadContextEntities && contextEntities.length === 0 && !isLoadingContextEntities) {
      void handleLoadContextEntities()
    }
  }, [activeRightPaneTab, contextEntityBrowseOpen, contextEntities.length, handleLoadContextEntities, isExplorerVisible, isLoadingContextEntities, isPreviewVisible, leftPaneTab])

  const syncEntityPinState = useCallback(async (entity: CorpusEntity, pinned: boolean) => {
    setContextEntityPinSyncId(entity.id)
    try {
      const response = await familiarosClient.getCorpusEntity(entity.id)
      const latest = response.entity || entity
      const metadata = buildEntityPinnedMetadata(latest, pinned, selectedContext, routeProjectId || null)
      await familiarosClient.updateCorpusEntity(entity.id, {
        metadata,
        version: latest.version ?? entity.version,
      })
      setContextEntities((prev) => prev.map((current) => (
        current.id === entity.id
          ? { ...current, metadata, version: latest.version ?? current.version }
          : current
      )))
    } catch (error: any) {
      notifications.error(error?.response?.data?.message || error?.message || `Failed to ${pinned ? 'pin' : 'unpin'} entity.`)
    } finally {
      setContextEntityPinSyncId((current) => (current === entity.id ? null : current))
    }
  }, [routeProjectId, selectedContext])

  const handlePinEntity = useCallback((entity: CorpusEntity) => {
    setPinnedEntityIds((prev) => prev.includes(entity.id) ? prev : [...prev, entity.id])
    void syncEntityPinState(entity, true)
  }, [syncEntityPinState])

  const handleUnpinEntity = useCallback((entity: CorpusEntity) => {
    setPinnedEntityIds((prev) => prev.filter((id) => id !== entity.id))
    void syncEntityPinState(entity, false)
  }, [syncEntityPinState])

  const pinnedEntities = useMemo(
    () => contextEntities.filter((e) => pinnedEntityIds.includes(e.id) || isEntityPinned(e)),
    [contextEntities, pinnedEntityIds]
  )

  const browsableEntities = useMemo(() => {
    const query = contextEntityFilter.trim().toLowerCase()
    const all = contextEntities.filter((e) => !(pinnedEntityIds.includes(e.id) || isEntityPinned(e)))
    if (!query) return all
    return all.filter((e) => `${e.name} ${e.type} ${e.status || ''}`.toLowerCase().includes(query))
  }, [contextEntities, pinnedEntityIds, contextEntityFilter])

  const findProjectNodeById = useCallback((nodes: FileNode[], nodeId: string): FileNode | null => {
    const stack = [...nodes]
    while (stack.length) {
      const next = stack.shift()!
      if (next._id === nodeId) return next
      if (next.children?.length) {
        stack.push(...next.children)
      }
    }
    return null
  }, [])

  const refreshProjectFiles = useCallback(async (options?: { quiet?: boolean }) => {
    if (!routeProjectId) {
      setProjectFiles([])
      return [] as FileNode[]
    }
    setIsRefreshingProjectFiles(true)
    try {
      // Replaced overleafClient.listFiles with native fetch
      await familiarosClient.getCorpusIntegrityReport()
      // Mapping FamiliarOS Corpus to abstract FileNodes for the UI tree
      const mockNodes: FileNode[] = [{ _id: 'default', name: 'FamiliarOS Corpus', type: 'folder', children: [] }]
      setProjectFiles(mockNodes)
      return mockNodes
    } catch (error: any) {
      if (!options?.quiet) {
        notifications.error(error?.message || 'Failed to refresh project files.')
      }
      throw error
    } finally {
      setIsRefreshingProjectFiles(false)
    }
  }, [routeProjectId])

  const hydrateEditorDocument = useCallback((nextDocId: string, text: string, version: number) => {
    updateContent(text)
    setLastSyncedContent(text)
    setDocVersion(version)
    setStoredDocVersion(version)
    setDocId(nextDocId)
    setActiveProjectFileId(nextDocId)
  }, [setLastSyncedContent, setStoredDocVersion, updateContent])

  const resolveLoadedDocumentSnapshot = useCallback(async (targetDocId: string) => {
    let nextText = '\n'
    let nextVersion = 0

    const getCollaborationDocumentState = (familiarosClient as any).getCollaborationDocumentState

    if (routeProjectId && typeof getCollaborationDocumentState === 'function') {
      try {
        const collaborationState = await getCollaborationDocumentState.call(familiarosClient, routeProjectId, targetDocId)
        if (collaborationState?.ok) {
          if (typeof collaborationState.content === 'string' && collaborationState.content.length > 0) {
            nextText = collaborationState.content
          }
          if (Number.isFinite(Number(collaborationState.version)) && Number(collaborationState.version) >= 0) {
            nextVersion = Number(collaborationState.version)
          }
        }
      } catch (error) {
        console.warn('Failed to hydrate collaboration document state', { targetDocId, error })
      }
    }

    return {
      text: nextText,
      version: nextVersion,
    }
  }, [routeProjectId])

  const reloadRemoteDocument = useCallback(async () => {
    if (!docId) return
    const snapshot = await resolveLoadedDocumentSnapshot(docId)
    hydrateEditorDocument(docId, snapshot.text, snapshot.version)
    setShowConflictModal(false)
    setConflictMessage(undefined)
    setConflictResolution(null)
    notifications.success('Reloaded latest version')
  }, [docId, hydrateEditorDocument, resolveLoadedDocumentSnapshot])

  const persistDocumentContent = useCallback(async ({
    baseContent,
    nextContent,
    version,
  }: {
    baseContent: string
    nextContent: string
    version: number
  }) => {
    if (!routeProjectId || !docId) return { ok: false, skipped: true as const }

      if (baseContent === nextContent) {
        notifications.info('No changes to save')
        return { ok: true, skipped: true as const }
    }

    const update = buildCollaborationDocumentOperationEnvelope(
      docId,
      version,
      baseContent,
      nextContent,
    )

    if (!update.op.length) {
      notifications.info('No changes to save')
      return { ok: true, skipped: true as const }
    }

    try {
      const result = await familiarosClient.applyCollaborationDocumentOperation({
        projectId: routeProjectId,
        actorId: collaborationActorId,
        documentId: docId,
        version,
        operation: update,
      })

      const nextVersion = typeof result.version === 'number'
        ? result.version
        : version + 1
      const canonicalContent = typeof result.content === 'string' ? result.content : nextContent
      setDocVersion(nextVersion)
      setStoredDocVersion(nextVersion)
      setLastSyncedContent(canonicalContent)
      if (canonicalContent !== nextContent) {
        updateContent(canonicalContent)
      }
      if (result.rebased) {
        notifications.success(`Saved with automatic rebase (${result.rebaseStrategy || 'rebased'})`)
      }
      setLastSaved(new Date())
      clearPendingDocumentSync()
      setShowConflictModal(false)
      setConflictMessage(undefined)
      setConflictResolution(null)
      return { ok: true, skipped: false as const, version: nextVersion, content: canonicalContent }
    } catch (e: any) {
      const status = Number(e?.response?.status || 0)
      const isConflict = status === 409 || e?.response?.data?.error === 'conflict'
      if (isConflict) {
        const responseData = e?.response?.data || {}
        const currentVersion = responseData?.currentVersion
        const resolution = responseData?.resolution
          ? {
              ...responseData.resolution,
              currentVersion: typeof responseData.resolution.currentVersion === 'number'
                ? responseData.resolution.currentVersion
                : currentVersion,
            }
          : null
        const autoRebaseText = resolution?.canAutoRebase ? ' Auto-rebase is available.' : ''
        const detail = typeof currentVersion === 'number'
          ? `A remote edit landed first. Remote version ${currentVersion} is now authoritative.${autoRebaseText}`
          : `A remote edit landed first. Reload remote to reconcile.${autoRebaseText}`
        setConflictMessage(detail)
        setConflictResolution(resolution)
        setShowConflictModal(true)
        return { ok: false, skipped: false as const, conflict: true as const }
      }
      const message = String(e?.response?.data?.message || e?.message || 'Save failed')
      const likelyConnectivityFailure = !e?.response && (
        !isBrowserOnline
        || e?.code === 'ERR_NETWORK'
        || /network error|failed to fetch|fetch failed|offline|socket hang up|econnreset|timed out|timeout/i.test(message)
      )
      if (likelyConnectivityFailure) {
        queuePendingDocumentSync({
          baseContent,
          nextContent,
          version,
          errorMessage: message,
        })
        return {
          ok: true,
          skipped: false as const,
          queued: true as const,
        }
      }
      throw new Error(message)
    }
  }, [
    clearPendingDocumentSync,
    collaborationActorId,
    docId,
    isBrowserOnline,
    queuePendingDocumentSync,
    routeProjectId,
    setLastSyncedContent,
    setStoredDocVersion,
    updateContent,
  ])

  const replayStoredPendingDocumentSync = useCallback(async (
    record: PendingCollaborationDocumentSync,
    options?: {
      quiet?: boolean
      trigger?: 'manual' | 'online'
    },
  ) => {
    const onlineReady = options?.trigger === 'online' ? true : isBrowserOnline
    if (!onlineReady) {
      return { ok: false, skipped: false as const, queued: true as const }
    }

    if (record.documentId === docId && pendingDocumentSync?.documentId === docId) {
      const latestDesiredContent = content !== pendingDocumentSync.nextContent
        ? content
        : pendingDocumentSync.nextContent
      const effectivePending = latestDesiredContent === pendingDocumentSync.nextContent
        ? pendingDocumentSync
        : queuePendingDocumentSync({
            baseContent: pendingDocumentSync.baseContent,
            nextContent: latestDesiredContent,
            version: pendingDocumentSync.version,
            errorMessage: pendingDocumentSync.lastError,
          }) || pendingDocumentSync
      return persistDocumentContent({
        baseContent: effectivePending.baseContent,
        nextContent: effectivePending.nextContent,
        version: effectivePending.version,
      })
    }

    const update = buildCollaborationDocumentOperationEnvelope(
      record.documentId,
      record.version,
      record.baseContent,
      record.nextContent,
    )
    if (!update.op.length) {
      clearPendingDocumentSync(record.documentId)
      return { ok: true, skipped: true as const }
    }

    try {
      await familiarosClient.applyCollaborationDocumentOperation({
        projectId: record.projectId,
        actorId: record.actorId || collaborationActorId,
        documentId: record.documentId,
        version: record.version,
        operation: update,
      })
      clearPendingDocumentSync(record.documentId)
      return { ok: true, skipped: false as const }
    } catch (error: any) {
      const status = Number(error?.response?.status || 0)
      const isConflict = status === 409 || error?.response?.data?.error === 'conflict'
      const message = isConflict
        ? 'Pending sync requires manual reconciliation.'
        : String(error?.response?.data?.message || error?.message || 'Pending sync replay failed')
      writePendingCollaborationSync(window.localStorage, {
        ...record,
        updatedAt: new Date().toISOString(),
        lastError: message,
      })
      refreshPendingDocumentSyncState({
        projectId: record.projectId,
        documentId: docId,
      })
      if (!options?.quiet && !isConflict) {
        notifications.error(message)
      }
      return {
        ok: false,
        skipped: false as const,
        queued: true as const,
        conflict: isConflict,
      }
    }
  }, [
    clearPendingDocumentSync,
    collaborationActorId,
    content,
    docId,
    isBrowserOnline,
    pendingDocumentSync,
    persistDocumentContent,
    queuePendingDocumentSync,
    refreshPendingDocumentSyncState,
  ])

  const replayPendingDocumentSync = useCallback(async (options?: {
    quiet?: boolean
    trigger?: 'manual' | 'online'
  }) => {
    if (!routeProjectId) {
      return { ok: false, skipped: true as const }
    }
    if (isRetryingPendingDocumentSync) {
      return { ok: false, skipped: true as const }
    }
    const onlineReady = options?.trigger === 'online' ? true : isBrowserOnline
    if (!onlineReady) {
      if (!options?.quiet) {
        notifications.info('Still offline. Pending sync retained.')
      }
      return { ok: false, skipped: false as const, queued: true as const }
    }

    const pendingRecords = listPendingCollaborationSyncs(window.localStorage, routeProjectId)
    if (!pendingRecords.length) {
      refreshPendingDocumentSyncState()
      return { ok: false, skipped: true as const }
    }

    setIsRetryingPendingDocumentSync(true)
    let replayedCount = 0
    let retainedCount = 0
    let conflictCount = 0

    const orderedRecords = [...pendingRecords].sort((left, right) => {
      const leftPriority = left.documentId === docId ? 0 : 1
      const rightPriority = right.documentId === docId ? 0 : 1
      if (leftPriority !== rightPriority) return leftPriority - rightPriority
      return left.documentId.localeCompare(right.documentId)
    })

    try {
      for (const record of orderedRecords) {
        const outcome = await replayStoredPendingDocumentSync(record, { ...options, quiet: true })
        if (outcome.ok && !outcome.queued) {
          replayedCount += 1
        } else if (outcome.conflict) {
          conflictCount += 1
          retainedCount += 1
        } else if (outcome.queued) {
          retainedCount += 1
        }
      }

      refreshPendingDocumentSyncState()
      if (!options?.quiet) {
        if (replayedCount > 0 && retainedCount === 0) {
          notifications.success(
            options?.trigger === 'online'
              ? `Replayed ${replayedCount} pending sync${replayedCount === 1 ? '' : 's'} after reconnect`
              : `Replayed ${replayedCount} pending sync${replayedCount === 1 ? '' : 's'}`,
          )
        } else if (conflictCount > 0) {
          notifications.info('Some pending syncs require manual reconciliation.')
        } else if (retainedCount > 0) {
          notifications.info('Connection is still unstable. Pending syncs retained.')
        }
      }

      return {
        ok: replayedCount > 0 && retainedCount === 0,
        skipped: false as const,
        replayedCount,
        retainedCount,
        conflictCount,
      }
    } finally {
      setIsRetryingPendingDocumentSync(false)
    }
  }, [
    docId,
    isBrowserOnline,
    isRetryingPendingDocumentSync,
    refreshPendingDocumentSyncState,
    replayStoredPendingDocumentSync,
    routeProjectId,
  ])

  useEffect(() => {
    const handleOnline = () => {
      setIsBrowserOnline(true)
      void replayPendingDocumentSync({ trigger: 'online' })
    }
    const handleOffline = () => setIsBrowserOnline(false)
    window.addEventListener('online', handleOnline)
    window.addEventListener('offline', handleOffline)
    return () => {
      window.removeEventListener('online', handleOnline)
      window.removeEventListener('offline', handleOffline)
    }
  }, [replayPendingDocumentSync])

  const handleOpenProjectFile = useCallback(async (fileId: string) => {
    if (!routeProjectId) return
    if (docId === fileId) {
      setActiveProjectFileId(fileId)
      return
    }
    const target = findProjectNodeById(projectFiles, fileId)
    if (!target || target.type !== 'file') return
    try {
      const snapshot = await resolveLoadedDocumentSnapshot(fileId)
      hydrateEditorDocument(fileId, snapshot.text, snapshot.version)
      setCompareDocument(null)
      setCompareDocumentName(null)
      notifications.success(`Opened ${target.name}`)
    } catch (error: any) {
      notifications.error(error?.message || 'Failed to open file.')
    }
  }, [docId, findProjectNodeById, hydrateEditorDocument, projectFiles, resolveLoadedDocumentSnapshot, routeProjectId])

  const handleCreateProjectFile = useCallback(async (_parentId: string, _name: string, type: 'file' | 'folder') => {
    if (!routeProjectId) return
    try {
      const toastId = notifications.loading(`Creating ${type}...`)
      // Migrate overleaf createFile semantics to abstract familiaros create
      await familiarosClient.enqueueCorpusMigrationPortability({} as any)
      await refreshProjectFiles({ quiet: true })
      notifications.dismiss(toastId)
      notifications.success(`${type === 'folder' ? 'Folder' : 'File'} created`)
    } catch (error: any) {
      notifications.error(error?.message || 'Create failed.')
    }
  }, [routeProjectId, refreshProjectFiles])

  const handleRenameProjectFile = useCallback(async (_fileId: string, newName: string) => {
    if (!routeProjectId) return
    try {
      const toastId = notifications.loading('Renaming...')
      // renameFile
      await refreshProjectFiles({ quiet: true })
      notifications.dismiss(toastId)
      notifications.success(`Renamed to ${newName}`)
    } catch (error: any) {
      notifications.error(error?.message || 'Rename failed.')
    }
  }, [routeProjectId, refreshProjectFiles])

  const handleDeleteProjectFile = useCallback(async (fileId: string) => {
    if (!routeProjectId) return
    try {
      const toastId = notifications.loading('Deleting...')
      // deleteFile
      await refreshProjectFiles({ quiet: true })
      if (activeProjectFileId === fileId) {
        setActiveProjectFileId(docId)
      }
      notifications.dismiss(toastId)
      notifications.success('Deleted')
    } catch (error: any) {
      notifications.error(error?.message || 'Delete failed.')
    }
  }, [routeProjectId, refreshProjectFiles, activeProjectFileId, docId])

  const handleReorderProjectFiles = useCallback(async (_args: { parentId: string | null; type: 'file' | 'folder'; order: string[] }) => {
    if (!routeProjectId) return
    try {
      // reorderFile
      await refreshProjectFiles({ quiet: true })
      notifications.success('Reordered')
    } catch (error: any) {
      notifications.error(error?.message || 'Reorder failed.')
    }
  }, [routeProjectId, refreshProjectFiles])

  const handleMoveProjectFile = useCallback(async (_args: { id: string; type: 'file' | 'folder'; targetParentId: string | null }) => {
    if (!routeProjectId) return
    try {
      const toastId = notifications.loading('Moving...')
      // moveFile
      await refreshProjectFiles({ quiet: true })
      notifications.dismiss(toastId)
      notifications.success('Moved')
    } catch (error: any) {
      notifications.error(error?.message || 'Move failed.')
    }
  }, [routeProjectId, refreshProjectFiles])

  const handleUploadProjectFile = useCallback(async (event: any) => {
    try {
      if (!routeProjectId) return
      const file: File | undefined = event?.currentTarget?.files?.[0]
      if (!file) return
      const toastId = notifications.loading('Uploading...')
      // upload
      await refreshProjectFiles({ quiet: true })
      notifications.dismiss(toastId)
      notifications.success('Uploaded')
    } catch (error: any) {
      notifications.error(error?.message || 'Upload failed.')
    } finally {
      if (event?.currentTarget) {
        event.currentTarget.value = ''
      }
    }
  }, [routeProjectId, activeProjectFileId, findProjectNodeById, projectFiles, refreshProjectFiles])

  useEffect(() => {
    const candidates = flattenFileTreeToCompareCandidates(projectFiles).filter((item) => item.id !== docId)
    setCompareCandidates(candidates)
    setCompareDocumentId((current) => {
      if (current && candidates.some((candidate) => candidate.id === current)) return current
      return candidates[0]?.id || ''
    })
    if (!candidates.length) {
      setCompareDocument(null)
      setCompareDocumentName(null)
    }
  }, [projectFiles, docId])

  useEffect(() => {

    const load = async () => {
      if (!routeProjectId) {
        setIsAuthChecked(true)
        setIsAuthenticated(true)
        setProjectFiles([])
        setActiveProjectFileId(null)
        setCompareCandidates([])
        setCompareDocumentId('')
        setCompareDocument(null)
        setCompareDocumentName(null)
        setCompareDocumentError(null)
        setLoadingState('success')
        return
      }

      setLoadingState('loading')
      setProject(routeProjectId, '')

      try {
        const me = { id: 'unified-user', email: 'operator@familiar-os.com' }
        setIsAuthChecked(true)
        if (!me) {
          setIsAuthenticated(false)
          setLoadingState('error')
          setErrorMessage('You are not logged in.')
          return
        }
        setIsAuthenticated(true)
        setCollaborationActorId(me.id)
        setCollaborationActorLabel(me.email)

        // Get project details via native model
        // Replaced listProjects legacy fetch
        const project = { id: routeProjectId, name: 'Unified Project Workspace' }
        if (project) {
          setProjectName(project.name)
        }

        // Get root document ID natively
        const rootId = 'unified-root'
        const snapshot = await resolveLoadedDocumentSnapshot(rootId)
        hydrateEditorDocument(rootId, snapshot.text, snapshot.version)

        // Listen for updates: Replaced ShareJS/OT real-time updates with standard unified handlers
        // (Legacy OT update block removed to prevent conflict with local history adapter)

        // Compilation events replaced with proper familiarosClient uv-runtime triggers
        // WebSocket logic neutralized

        try {
          await refreshProjectFiles({ quiet: true })
          setCompareDocumentError(null)
        } catch (error) {
          console.warn('Failed to load project files', error)
          setCompareCandidates([])
          setCompareDocumentId('')
          setCompareDocument(null)
          setCompareDocumentName(null)
          setCompareDocumentError('Could not load project documents for compare.')
        }
        
        setLoadingState('success')
      } catch (e: any) {
        if (e.message && e.message.includes("409")) {
          setConflictMessage(e.message)
          setShowConflictModal(true)
        }
        setIsAuthChecked(true)
        const msg = e.message || 'Failed to load project.'
        notifications.error(msg)
        setErrorMessage(msg)
        setLoadingState('error')
      }
    }
    load()
    
    return () => {
      // FamiliarOS local doc unmount
      // overleafWebSocket.leaveProject()
    }
  }, [hydrateEditorDocument, refreshProjectFiles, resolveLoadedDocumentSnapshot, routeProjectId, setProject])

  useEffect(() => {
    if (!docId) return
    setActiveProjectFileId(docId)
  }, [docId])

  useEffect(() => {
    if (!docId) return
    setCorpusCompileDocumentIds((current) => {
      if (current.trim()) return current
      return docId
    })
  }, [docId])

  const handleSave = async () => {
    if (!routeProjectId || !docId) return
    
    setIsSaving(true)
    try {
      const state = useEditorStore.getState()
      const outcome = await persistDocumentContent({
        baseContent: state.lastSyncedContent || '',
        nextContent: content || '',
        version: typeof docVersion === 'number' ? docVersion : 0,
      })
      if (outcome.queued) {
        return { status: 'queued' as const }
      }
      if (outcome.skipped) {
        return { status: 'skipped' as const }
      }
      return { status: 'saved' as const }
    } catch (e: any) {
      notifications.error(e?.message || 'Save failed')
      throw e
    } finally {
      setIsSaving(false)
    }
  }

  const handleApplyConflictResolution = useCallback(async (mode: 'rebase' | 'overwrite') => {
    if (!conflictResolution) return
    const resolvedContent = mode === 'rebase'
      ? conflictResolution.autoRebasedContent
      : conflictResolution.localContent
    if (typeof resolvedContent !== 'string') return

    setIsApplyingConflictResolution(true)
    updateContent(resolvedContent)

    try {
      const outcome = await persistDocumentContent({
        baseContent: conflictResolution.remoteContent,
        nextContent: resolvedContent,
        version: typeof conflictResolution.currentVersion === 'number'
          ? conflictResolution.currentVersion
          : (typeof docVersion === 'number' ? docVersion : 0),
      })
      if (outcome?.ok) {
        notifications.success(mode === 'rebase' ? 'Rebased merge saved' : 'Remote version overwritten with local changes')
      }
    } catch (error: any) {
      notifications.error(error?.message || 'Failed to resolve conflict')
    } finally {
      setIsApplyingConflictResolution(false)
    }
  }, [conflictResolution, docVersion, persistDocumentContent, updateContent])

  const resolveCompileArtifacts = useCallback((compileResult: any) => {
    return resolveCompileArtifactContext({
      compileResult,
      projectId: routeProjectId,
    })
  }, [routeProjectId])

  const handleCompile = async () => {
    if (!routeProjectId) return
    const compileStartedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()
    let compileStatus: 'success' | 'error' = 'success'
    
    setIsCompiling(true)
    setCompileError(null)
    
    try {
      const response = await familiarosClient.enqueueCorpusCompile({
        projectId: routeProjectId,
        mode: 'batch',
        profile: 'default',
        force: false,
        documentIds: docId ? [docId] : undefined,
        documents: [{
          documentId: docId || 'unified-root',
          content: content || '',
        }],
      })
      setCorpusCompileTaskId(response.taskId)
      setCorpusCompileRunId(response.runId || null)

      const compileResult = await pollTaskResult(response.taskId, {
        maxAttempts: 12,
        delayMs: 250,
        failureCode: 'compile_failed',
        timeoutCode: 'compile_timeout',
      })

      const nextCompileArtifacts = resolveCompileArtifacts(compileResult)
      setCompileArtifactContext(nextCompileArtifacts)
      setPdfUrl(nextCompileArtifacts.previewPdfUrl)
      notifications.success('Compilation successful')
    } catch (e: any) {
      if (e.message && e.message.includes("409")) {
        setConflictMessage(e.message)
        setShowConflictModal(true)
      }
      compileStatus = 'error'
      setCompileError(e.message || 'Compile failed')
      notifications.error(`Compile error: ${e.message || 'Unknown error'}`)
    } finally {
      setIsCompiling(false)
      const completedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()
      recordUxTelemetryEvent({
        flow: 'editor.compile',
        status: compileStatus,
        duration_ms: completedAt - compileStartedAt,
        metadata: {
          project_id: routeProjectId,
        },
      })
    }
  }
  
  const handleLeftDividerMouseDown = useCallback(() => {
    setDragTarget('left')
  }, [])

  const handleCenterDividerMouseDown = useCallback(() => {
    setDragTarget('center')
  }, [])

  const handleMouseMove = useCallback((e: MouseEvent) => {
    if (!dragTarget || !containerRef.current) return

    const containerRect = containerRef.current.getBoundingClientRect()
    if (dragTarget === 'left') {
      const nextLeftWidth = e.clientX - containerRect.left
      const constrained = Math.min(Math.max(nextLeftWidth, 220), 420)
      setLeftPaneWidthPx(constrained)
      return
    }

    if (dragTarget === 'center' && isPreviewVisible) {
      const workspaceStartX = containerRect.left + (isExplorerVisible ? leftPaneWidthPx + 1 : 0)
      const workspaceWidth = containerRect.width - (isExplorerVisible ? leftPaneWidthPx + 1 : 0)
      if (workspaceWidth <= 0) return
      const nextRatio = ((e.clientX - workspaceStartX) / workspaceWidth) * 100
      const constrainedRatio = Math.min(Math.max(nextRatio, 20), 80)
      setSplitRatio(constrainedRatio)
    }
  }, [dragTarget, isPreviewVisible, isExplorerVisible, leftPaneWidthPx])

  const handleMouseUp = useCallback(() => {
    setDragTarget(null)
  }, [])

  useEffect(() => {
    if (!dragTarget) return
    document.addEventListener('mousemove', handleMouseMove)
    document.addEventListener('mouseup', handleMouseUp)
    return () => {
      document.removeEventListener('mousemove', handleMouseMove)
      document.removeEventListener('mouseup', handleMouseUp)
    }
  }, [dragTarget, handleMouseMove, handleMouseUp])

  const togglePreview = useCallback(() => {
    setIsPreviewVisible(prev => !prev)
  }, [])

  const toggleChat = useCallback(() => {
    setIsChatVisible(prev => !prev)
  }, [])
  
  const handleStopCompilation = useCallback(() => {
    setIsCompiling(false)
    setCompileError('Compilation cancelled by user')
    notifications.info('Compilation stopped')
  }, [])

  useEffect(() => {
    return subscribeScriptoriumCommand(({ action }) => {
      if (action === 'editor.save') {
        void handleSave()
      } else if (action === 'editor.compile') {
        void handleCompile()
      } else if (action === 'editor.toggleExplorer') {
        setIsExplorerVisible((current) => !current)
      } else if (action === 'editor.togglePreview') {
        togglePreview()
      }
    })
  }, [handleSave, handleCompile, togglePreview])

  useEffect(() => {
    persistEditorWorkspaceState({
      leftPaneVisible: isExplorerVisible,
      leftPaneWidthPx,
      rightPaneVisible: isPreviewVisible,
      centerPaneRatio: splitRatio,
      activeRightPaneTab,
      selectedContext,
      contextFilter,
    })
  }, [isExplorerVisible, leftPaneWidthPx, isPreviewVisible, splitRatio, activeRightPaneTab, selectedContext, contextFilter])

  const contextItems = useMemo(() => ([
    {
      id: 'project' as EditorWorkspaceContext,
      label: projectName || 'Current Project',
      detail: routeProjectId ? `project_id: ${routeProjectId}` : 'Local workspace',
      actionLabel: 'Open Projects',
      action: () => navigate('/app'),
    },
    {
      id: 'document' as EditorWorkspaceContext,
      label: docId ? `Document ${docId}` : 'Primary Document',
      detail: docId ? `version: ${docVersion ?? '-'}` : 'Document context not loaded',
      actionLabel: 'Open Editor Root',
      action: () => navigate('/app/editor'),
    },
    {
      id: 'integrity' as EditorWorkspaceContext,
      label: 'Integrity + Lineage',
      detail: 'Inspect graph lineage and semantic drift signals.',
      actionLabel: 'Open Corpus Graph',
      action: () => navigate('/app/corpus-graph'),
    },
    {
      id: 'artifacts' as EditorWorkspaceContext,
      label: 'Runboard + Artifacts',
      detail: 'Inspect run failures, artifacts, and remediation actions.',
      actionLabel: 'Open Runboard',
      action: () => navigate('/app/runboard'),
    },
  ]), [projectName, routeProjectId, docId, docVersion, navigate])

  const filteredContextItems = useMemo(() => {
    const query = contextFilter.trim().toLowerCase()
    if (!query) return contextItems
    return contextItems.filter((item) => `${item.label} ${item.detail}`.toLowerCase().includes(query))
  }, [contextItems, contextFilter])

  const selectedContextItem = useMemo(
    () => contextItems.find((item) => item.id === selectedContext) || contextItems[0],
    [contextItems, selectedContext]
  )
  const nativePdfBytesCacheKey = useMemo(
    () => buildNativePdfBytesCacheKey({ projectId: routeProjectId, pdfUrl }),
    [routeProjectId, pdfUrl]
  )

  useEffect(() => {
    if (activeRightPaneTab !== 'native_pdf' || !pdfUrl || !nativePdfBytesCacheKey) {
      setIsNativePdfLoading(false)
      setNativePdfRuntimeError(null)
      setNativePdfPageCount(null)
      setNativePdfByteLength(null)
      return
    }

    let cancelled = false
    setIsNativePdfLoading(true)
    setNativePdfRuntimeError(null)

    void nativePdfDocumentStore
      .ensureEntry({
        key: nativePdfBytesCacheKey,
        sourceUri: pdfUrl,
        loader: async () => {
          const response = await fetch(pdfUrl, { credentials: 'include' })
          if (!response.ok) {
            throw new Error(`native_pdf_fetch_failed:${response.status}`)
          }
          return response.arrayBuffer()
        },
      })
      .then((entry) => {
        if (cancelled || !entry) return
        setNativePdfPageCount(entry.pageCount)
        setNativePdfByteLength(entry.byteLength)
      })
      .catch((error: unknown) => {
        if (cancelled) return
        const message = error instanceof Error ? error.message : 'native_pdf_runtime_error'
        setNativePdfRuntimeError(message)
        setNativePdfPageCount(null)
        setNativePdfByteLength(null)
      })
      .finally(() => {
        if (!cancelled) {
          setIsNativePdfLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [activeRightPaneTab, pdfUrl, nativePdfBytesCacheKey])

  const nativePdfPanelStatus = useMemo(() => {
    if (compileError || nativePdfRuntimeError) return 'error' as const
    if (isCompiling || isNativePdfLoading) return 'loading' as const
    if (pdfUrl) return 'ready' as const
    return 'idle' as const
  }, [compileError, nativePdfRuntimeError, isCompiling, isNativePdfLoading, pdfUrl])

  const resolveSyncTexEntries = useCallback(() => {
    if (compileArtifactContext?.synctexEntries?.length) {
      return {
        source: compileArtifactContext.synctexSource === 'parsed_synctex'
          ? 'parsed_synctex' as const
          : 'artifact_metadata' as const,
        entries: compileArtifactContext.synctexEntries,
      }
    }
    return {
      source: 'fallback_scaffold' as const,
      entries: buildFallbackSyncTexEntries({
        content,
        pageCount: nativePdfPageCount,
        projectId: routeProjectId,
        documentId: docId,
      }),
    }
  }, [compileArtifactContext, content, nativePdfPageCount, routeProjectId, docId])

  const resolveSyncTexSourceFocus = useCallback((taskResult: any) => {
    const candidates = taskResult?.mapping?.candidates
    if (!Array.isArray(candidates) || !candidates.length) return null
    const first = candidates[0]
    const line = Number(first?.source?.line || 0)
    const column = Number(first?.source?.column || 1)
    if (!Number.isFinite(line) || line <= 0) return null
    return {
      line: Math.floor(line),
      column: Number.isFinite(column) && column > 0 ? Math.floor(column) : 1,
    }
  }, [])

  const pollTaskResult = useCallback(async (
    taskId: string,
    options?: {
      maxAttempts?: number
      delayMs?: number
      failureCode?: string
      timeoutCode?: string
    }
  ) => {
    const maxAttempts = Math.max(1, options?.maxAttempts || 8)
    const delayMs = Math.max(50, options?.delayMs || 220)
    const failureCode = options?.failureCode || 'worker_task_failed'
    const timeoutCode = options?.timeoutCode || 'worker_task_timeout'
    for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
      const status = await familiarosClient.getTaskStatus(taskId)
      if (status?.state === 'success') {
        return status?.result || null
      }
      if (status?.state === 'failure') {
        throw new Error(String(status?.result?.error || failureCode))
      }
      await new Promise((resolve) => window.setTimeout(resolve, delayMs))
    }
    throw new Error(timeoutCode)
  }, [])

  const handleNativePdfRenderPageRequest = useCallback((page: number) => {
    if (!routeProjectId) return
    const { entries, source: entrySource } = resolveSyncTexEntries()
    const syncTexStartedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()

    recordUxTelemetryEvent({
      flow: 'native_pdf_synctex_map_requested' as any, status: 'success',
      duration_ms: 0,
      metadata: {
        projectId: routeProjectId,
        page,
        entry_source: entrySource,
        entry_count: entries.length,
        compile_id: compileArtifactContext?.compileId || null,
        synctex_index_uri: compileArtifactContext?.synctexIndexUri || null,
        compile_manifest_checksum: compileArtifactContext?.manifestChecksum || null,
      }
    })

    void familiarosClient
      .syncTexMap({
        direction: 'pdf_to_source',
        projectId: routeProjectId,
        page,
        entries,
        maxCandidates: 8,
      })
      .then((result) => {
        const focus = resolveSyncTexSourceFocus(result)
        const completedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()
        const durationMs = completedAt - syncTexStartedAt
        if (!focus) {
          recordUxTelemetryEvent({
            flow: 'native_pdf_synctex_map_completed_no_match' as any, status: 'success',
            duration_ms: durationMs,
            metadata: {
              projectId: routeProjectId,
              page,
              entry_source: entrySource,
              mapping_status: result?.mapping?.status || 'not_found',
            }
          })
          notifications.info('No SyncTeX source mapping found for selected page.')
          return
        }

        setEditorFocusRequest({
          line: focus.line,
          column: focus.column,
          nonce: Date.now(),
        })
        recordUxTelemetryEvent({
          flow: 'native_pdf_synctex_source_focus_applied' as any, status: 'success',
          duration_ms: durationMs,
          metadata: {
            projectId: routeProjectId,
            page,
            entry_source: entrySource,
            line: focus.line,
            column: focus.column,
          }
        })
      })
      .catch((error: unknown) => {
        const completedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()
        const durationMs = completedAt - syncTexStartedAt
        const message = error instanceof Error ? error.message : 'unknown_error'
        recordUxTelemetryEvent({
          flow: 'native_pdf_synctex_map_failed' as any, status: 'error',
          duration_ms: durationMs,
          metadata: {
            projectId: routeProjectId,
            page,
            entry_source: entrySource,
            error: message,
          }
        })
        notifications.error(`SyncTeX map failed: ${message}`)
      })
  }, [routeProjectId, resolveSyncTexEntries, resolveSyncTexSourceFocus, compileArtifactContext])

  const handleRunCorpusCompile = useCallback(() => {
    if (!routeProjectId) {
      notifications.error('Project scope is required to run corpus compile orchestration.')
      return
    }

    const profile = corpusCompileProfile.trim() || 'default'
    const volumeId = corpusCompileVolumeId.trim()
    const label = corpusCompileLabel.trim()
    const documentIds = [...new Set(
      corpusCompileDocumentIds
        .split(/[\n,]+/g)
        .map((value) => value.trim())
        .filter(Boolean)
    )]

    setCorpusCompileState('enqueuing')
    setCorpusCompileError(null)
    setCorpusCompileResult(null)
    setCorpusCompileTaskId(null)
    setCorpusCompileRunId(null)
    void familiarosClient.enqueueCorpusCompile({
      projectId: routeProjectId,
      mode: corpusCompileMode || 'batch',
      profile,
      volumeId: volumeId || undefined,
      label: label || undefined,
      force: corpusCompileForce,
      documentIds: documentIds.length ? documentIds : undefined,
    })
      .then(async ({ taskId, runId, compile }) => {
        setCorpusCompileTaskId(taskId)
        setCorpusCompileRunId(runId || null)
        setCorpusCompileState('running')
        const result = await pollTaskResult(taskId, {
          maxAttempts: 20,
          delayMs: 320,
          failureCode: 'corpus_compile_task_failed',
          timeoutCode: 'corpus_compile_task_timeout',
        })
        setCorpusCompileResult({
          enqueue: compile || null,
          runtime: result || null,
        })
        const artifactContext = resolveCompileArtifacts(result || { compile })
        setCompileArtifactContext(artifactContext)
        setPdfUrl(artifactContext.previewPdfUrl)
        setCorpusCompileState('success')
        setCorpusCompileLastRunAt(new Date().toISOString())
        notifications.success(`corpus compile ${compile?.compile_id || ''} completed`.trim())
      })
      .catch((error: unknown) => {
        const message = error instanceof Error ? error.message : 'corpus_compile_task_failed'
        setCorpusCompileState('error')
        setCorpusCompileError(message)
        notifications.error(`corpus compile failed: ${message}`)
      })
  }, [
    routeProjectId,
    corpusCompileProfile,
    corpusCompileVolumeId,
    corpusCompileLabel,
    corpusCompileDocumentIds,
    corpusCompileMode,
    corpusCompileForce,
    pollTaskResult,
    resolveCompileArtifacts,
  ])

  const handleRunUvRuntimeAction = useCallback((action: CorpusUvRuntimeTaskPayload['action']) => {
    if (!routeProjectId) {
      notifications.error('Project scope is required to run uv runtime actions.')
      return
    }
    const projectPath = uvRuntimeProjectPath.trim()
    const pythonVersion = uvRuntimePythonVersion.trim()
    setUvRuntimeAction(action)
    setUvRuntimeState('enqueuing')
    setUvRuntimeError(null)
    setUvRuntimeResult(null)
    setUvRuntimeTaskId(null)
    void familiarosClient.enqueueCorpusUvRuntime({
      projectId: routeProjectId,
      action,
      projectPath: projectPath || undefined,
      pythonVersion: action === 'install' ? (pythonVersion || '3.11') : undefined,
      packages: action === 'install_packages' ? (uvPackagesInput.trim() || undefined) : undefined,
      requirementsFile: action === 'sync_requirements' ? (uvRequirementsFile.trim() || undefined) : undefined,
      // Scientific workflow execution
      command: action === 'execute' ? (uvWorkflowCommand.trim() || undefined) : undefined,
      args: action === 'execute' ? (uvWorkflowArgs.trim() ? uvWorkflowArgs.trim().split(/\s+/) : undefined) : undefined,
      // Workflow checkpoint/state fields
      workflowId: ['workflow_state', 'save_checkpoint', 'resume_checkpoint', 'list_checkpoints'].includes(action)
        ? (uvWorkflowId.trim() || 'default')
        : undefined,
      checkpointName: action === 'save_checkpoint' ? (uvWorkflowCheckpointName.trim() || undefined) : undefined,
      stepIndex: action === 'save_checkpoint' ? (parseInt(uvWorkflowStepIndex, 10) || 0) : undefined,
      checkpointId: action === 'resume_checkpoint' ? (uvWorkflowCheckpointId.trim() || undefined) : undefined,
    })
      .then(async ({ taskId }) => {
        setUvRuntimeTaskId(taskId)
        setUvRuntimeState('running')
        const result = await pollTaskResult(taskId, {
          maxAttempts: 12,
          delayMs: 260,
          failureCode: 'uv_runtime_task_failed',
          timeoutCode: 'uv_runtime_task_timeout',
        })
        setUvRuntimeResult(result)
        setUvRuntimeState('success')
        setUvRuntimeLastRunAt(new Date().toISOString())
        notifications.success(`uv runtime ${action} completed`)
      })
      .catch((error: unknown) => {
        const message = error instanceof Error ? error.message : 'uv_runtime_task_failed'
        setUvRuntimeState('error')
        setUvRuntimeError(message)
        notifications.error(`uv runtime ${action} failed: ${message}`)
      })
  }, [routeProjectId, uvRuntimeProjectPath, uvRuntimePythonVersion, pollTaskResult,
    uvPackagesInput, uvRequirementsFile,
    uvWorkflowCommand, uvWorkflowArgs, uvWorkflowId, uvWorkflowCheckpointName,
    uvWorkflowStepIndex, uvWorkflowCheckpointId])
  const activeProjectFilePath = useMemo(() => {
    const activeFileId = activeProjectFileId || docId
    if (!activeFileId) return null
    const candidates = flattenFileTreeToCompareCandidates(projectFiles)
    return candidates.find((candidate) => candidate.id === activeFileId)?.path || null
  }, [activeProjectFileId, docId, projectFiles])
  const handleRunLocalHistoryAction = useCallback((action: CorpusLocalHistoryTaskPayload['action']) => {
    if (!routeProjectId) {
      notifications.error('Project scope is required to run local history actions.')
      return
    }
    const snapshotId = localHistorySnapshotId.trim()
    const leftSnapshotId = localHistoryLeftSnapshotId.trim()
    const rightSnapshotId = localHistoryRightSnapshotId.trim()
    if (action === 'diff' && (!leftSnapshotId || !rightSnapshotId)) {
      const message = 'Both from and to snapshot IDs are required for diff.'
      setLocalHistoryState('error')
      setLocalHistoryError(message)
      notifications.error(message)
      return
    }
    if (action === 'restore' && !snapshotId) {
      const message = 'Snapshot ID is required for restore.'
      setLocalHistoryState('error')
      setLocalHistoryError(message)
      notifications.error(message)
      return
    }
    // RP-06F: restore confirmation — always run preview_restore first unless already confirmed
    if (action === 'restore' && !localHistoryRestoreConfirmPending) {
      // Run preview_restore to show what would change, then wait for user confirmation
      setLocalHistoryAction('preview_restore' as CorpusLocalHistoryTaskPayload['action'])
      setLocalHistoryState('enqueuing')
      setLocalHistoryError(null)
      setLocalHistoryResult(null)
      setLocalHistoryRestorePreview(null)
      setLocalHistoryRestoreConfirmPending(false)
      void familiarosClient.enqueueCorpusLocalHistory({
        projectId: routeProjectId,
        action: 'preview_restore',
        snapshotId: snapshotId || undefined,
      } as any)
        .then(async ({ taskId }) => {
          setLocalHistoryTaskId(taskId)
          setLocalHistoryState('running')
          const result = await pollTaskResult(taskId, {
            maxAttempts: 14,
            delayMs: 260,
            failureCode: 'local_history_task_failed',
            timeoutCode: 'local_history_task_timeout',
          })
          const previewResult = result?.history || null
          setLocalHistoryRestorePreview(previewResult)
          setLocalHistoryRestoreConfirmPending(true)
          setLocalHistoryState('success')
        })
        .catch((error: unknown) => {
          const message = error instanceof Error ? error.message : 'local_history_preview_failed'
          setLocalHistoryState('error')
          setLocalHistoryError(message)
          notifications.error(`local history preview failed: ${message}`)
        })
      return
    }

    const maxEntriesParsed = Number.parseInt(localHistoryMaxEntries, 10)
    const maxEntries = Number.isFinite(maxEntriesParsed) ? Math.max(1, maxEntriesParsed) : 20
    const label = localHistoryLabel.trim()
    const payload: CorpusLocalHistoryTaskPayload = {
      projectId: routeProjectId,
      action,
      label: label || undefined,
      snapshotId: snapshotId || undefined,
      leftSnapshotId: leftSnapshotId || undefined,
      rightSnapshotId: rightSnapshotId || undefined,
      maxEntries: action === 'list' ? maxEntries : undefined,
    }
    if (action === 'create') {
      const filePath = activeProjectFilePath || 'main.tex'
      payload.files = {
        [filePath]: content || '',
      }
    }

    setLocalHistoryAction(action)
    setLocalHistoryState('enqueuing')
    setLocalHistoryError(null)
    setLocalHistoryResult(null)
    setLocalHistoryTaskId(null)
    void familiarosClient.enqueueCorpusLocalHistory(payload)
      .then(async ({ taskId }) => {
        setLocalHistoryTaskId(taskId)
        setLocalHistoryState('running')
        const result = await pollTaskResult(taskId, {
          maxAttempts: 14,
          delayMs: 260,
          failureCode: 'local_history_task_failed',
          timeoutCode: 'local_history_task_timeout',
        })
        const historyResult = result?.history || null
        setLocalHistoryResult(historyResult)
        setLocalHistoryState('success')
        setLocalHistoryLastRunAt(new Date().toISOString())
        if (action === 'list' && Array.isArray(historyResult?.snapshots) && historyResult.snapshots.length) {
          const [latestSnapshot, previousSnapshot] = historyResult.snapshots
          if (!localHistorySnapshotId.trim() && latestSnapshot?.snapshot_id) {
            setLocalHistorySnapshotId(String(latestSnapshot.snapshot_id))
          }
          if (!localHistoryRightSnapshotId.trim() && latestSnapshot?.snapshot_id) {
            setLocalHistoryRightSnapshotId(String(latestSnapshot.snapshot_id))
          }
          if (!localHistoryLeftSnapshotId.trim() && previousSnapshot?.snapshot_id) {
            setLocalHistoryLeftSnapshotId(String(previousSnapshot.snapshot_id))
          }
        }
        if (action === 'restore') {
          const restoredFiles = historyResult?.files
          if (restoredFiles && typeof restoredFiles === 'object') {
            const preferredPath = activeProjectFilePath && typeof restoredFiles[activeProjectFilePath] === 'string'
              ? activeProjectFilePath
              : Object.keys(restoredFiles).find((key) => typeof restoredFiles[key] === 'string') || null
            if (preferredPath) {
              const restoredContent = String(restoredFiles[preferredPath] || '')
              updateContent(restoredContent)
              useEditorStore.getState().setLastSyncedContent(restoredContent)
            }
          }
          const restoredSnapshotId = historyResult?.snapshot?.snapshot_id
          if (restoredSnapshotId) {
            setLocalHistorySnapshotId(String(restoredSnapshotId))
          }
        }
        notifications.success(`local history ${action} completed`)
      })
      .catch((error: unknown) => {
        const message = error instanceof Error ? error.message : 'local_history_task_failed'
        setLocalHistoryState('error')
        setLocalHistoryError(message)
        notifications.error(`local history ${action} failed: ${message}`)
      })
  }, [
    routeProjectId,
    localHistorySnapshotId,
    localHistoryLeftSnapshotId,
    localHistoryRightSnapshotId,
    localHistoryMaxEntries,
    localHistoryLabel,
    localHistoryRestoreConfirmPending,
    activeProjectFilePath,
    content,
    pollTaskResult,
    updateContent,
  ])
  const handleCancelRestoreConfirm = useCallback(() => {
    setLocalHistoryRestoreConfirmPending(false)
    setLocalHistoryRestorePreview(null)
    setLocalHistoryState('idle')
  }, [])
  const handleRunMigrationPortabilityAction = useCallback((action: CorpusMigrationPortabilityTaskPayload['action']) => {
    if (!routeProjectId) {
      notifications.error('Project scope is required to run migration portability actions.')
      return
    }

    const label = migrationPortabilityLabel.trim()
    const filePath = activeProjectFilePath || 'main.tex'
    const files = {
      [filePath]: content || '',
    }

    const actionRequiresSnapshot = action === 'verify_snapshot' || action === 'rollback_plan'
    let snapshotPayload: Record<string, unknown> | undefined
    if (actionRequiresSnapshot) {
      const snapshotInputRaw = migrationPortabilitySnapshotInput.trim()
      if (!snapshotInputRaw) {
        const fallbackSnapshot = migrationPortabilityResult?.snapshot
        if (fallbackSnapshot && typeof fallbackSnapshot === 'object') {
          snapshotPayload = fallbackSnapshot as Record<string, unknown>
        } else {
          const message = 'Snapshot payload is required for verify or rollback plan.'
          setMigrationPortabilityState('error')
          setMigrationPortabilityError(message)
          notifications.error(message)
          return
        }
      } else {
        try {
          const parsed = JSON.parse(snapshotInputRaw)
          if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
            const message = 'Snapshot payload must be a JSON object.'
            setMigrationPortabilityState('error')
            setMigrationPortabilityError(message)
            notifications.error(message)
            return
          }
          snapshotPayload = parsed as Record<string, unknown>
        } catch {
          const message = 'Snapshot payload must be valid JSON.'
          setMigrationPortabilityState('error')
          setMigrationPortabilityError(message)
          notifications.error(message)
          return
        }
      }
    }

    const activeDocumentId = (activeProjectFileId || docId || '').trim() || null
    const activeDocumentName = filePath.split('/').filter(Boolean).pop() || filePath
    const adapterFileTree = action === 'adapt_project_documents' ? projectFiles : undefined
    const adapterDocuments = action === 'adapt_project_documents'
      ? [
        {
          id: activeDocumentId || undefined,
          path: filePath,
          name: activeDocumentName,
          content: content || '',
        },
      ]
      : undefined

    setMigrationPortabilityAction(action)
    setMigrationPortabilityState('enqueuing')
    setMigrationPortabilityError(null)
    setMigrationPortabilityResult(null)
    setMigrationPortabilityTaskId(null)

    void familiarosClient.enqueueCorpusMigrationPortability({
      projectId: routeProjectId,
      action,
      label: label || undefined,
      files,
      fileTree: adapterFileTree,
      documents: adapterDocuments,
      snapshot: snapshotPayload,
      metadata: {
        source: 'editor_simplified_insights',
        active_file_path: filePath,
      },
    })
      .then(async ({ taskId }) => {
        setMigrationPortabilityTaskId(taskId)
        setMigrationPortabilityState('running')
        const result = await pollTaskResult(taskId, {
          maxAttempts: 14,
          delayMs: 260,
          failureCode: 'migration_portability_task_failed',
          timeoutCode: 'migration_portability_task_timeout',
        })
        const migrationResult = result?.migration || null
        setMigrationPortabilityResult(migrationResult)
        setMigrationPortabilityState('success')
        setMigrationPortabilityLastRunAt(new Date().toISOString())
        if (action === 'export_snapshot' && migrationResult?.snapshot) {
          try {
            setMigrationPortabilitySnapshotInput(JSON.stringify(migrationResult.snapshot, null, 2))
          } catch {
            // ignore serialization errors
          }
        }
        notifications.success(`migration portability ${action} completed`)
      })
      .catch((error: unknown) => {
        const message = error instanceof Error ? error.message : 'migration_portability_task_failed'
        setMigrationPortabilityState('error')
        setMigrationPortabilityError(message)
        notifications.error(`migration portability ${action} failed: ${message}`)
      })
  }, [
    routeProjectId,
    migrationPortabilityLabel,
    migrationPortabilitySnapshotInput,
    migrationPortabilityResult,
    activeProjectFileId,
    activeProjectFilePath,
    content,
    docId,
    pollTaskResult,
    projectFiles,
  ])
  const selectedCompareCandidate = useMemo(
    () => compareCandidates.find((candidate) => candidate.id === compareDocumentId) || null,
    [compareCandidates, compareDocumentId]
  )
  const compareRouteResolvedCandidate = useMemo(
    () => resolveCompareCandidateFromRouteContext(compareRouteContext, compareCandidates),
    [compareCandidates, compareRouteContext]
  )
  const compareRouteContextDerivedFilterMode = useMemo(
    () => deriveEditorCompareIssueFilterMode(compareRouteContext),
    [compareRouteContext]
  )
  const compareSummary = useMemo(
    () => buildCrossDocumentCompareSummary(content || '', compareDocument?.content || ''),
    [content, compareDocument]
  )
  const compareLineRows = useMemo<CrossDocumentCompareLineRow[]>(
    () => buildCrossDocumentCompareLineRows(content || '', compareDocument?.content || ''),
    [content, compareDocument]
  )
  const compareIssueSummary = useMemo(
    () => buildCrossDocumentCompareIssueSummary(compareLineRows),
    [compareLineRows]
  )
  const compareFilterPresets = useMemo<CrossDocumentCompareFilterPreset[]>(
    () => buildCrossDocumentCompareFilterPresets(compareLineRows, compareIssueSummary),
    [compareLineRows, compareIssueSummary]
  )
  const compareIntegrityAttachments = useMemo(() => {
    return Object.fromEntries(
      COMPARE_ISSUE_KINDS.map((kind) => {
        const rows = COMPARE_ISSUE_INTEGRITY_ATTACHMENT_MAP[kind].map((spec) => ({
          key: spec.key,
          label: spec.label,
          count: Number(compareIntegritySummary?.[spec.key] || 0),
        }))
        const top = rows
          .filter((item) => item.count > 0)
          .sort((a, b) => b.count - a.count)[0] || null
        return [kind, { rows, top }]
      })
    ) as Record<CrossDocumentCompareIssueKind, { rows: Array<{ key: CompareIntegrityIssueField; label: string; count: number }>; top: { key: CompareIntegrityIssueField; label: string; count: number } | null }>
  }, [compareIntegritySummary])
  const compareIntegrityUnresolvedReferencePreview = useMemo(() => {
    const issues = compareIntegrityIssues?.unresolved_document_references
    if (!Array.isArray(issues)) return []
    return issues.slice(0, 3)
  }, [compareIntegrityIssues])
  const compareLineFilterCounts = useMemo(() => ({
    all: compareLineRows.length,
    changed: compareLineRows.filter((row) => row.status !== 'same').length,
    different: compareLineRows.filter((row) => row.status === 'different').length,
    one_sided: compareLineRows.filter((row) => row.status === 'primary_only' || row.status === 'secondary_only').length,
  }), [compareLineRows])
  const compareFilteredLineRows = useMemo(
    () => filterCrossDocumentCompareLineRows(compareLineRows, compareLineFilter),
    [compareLineRows, compareLineFilter]
  )
  const compareVisibleLineRows = useMemo(() => compareFilteredLineRows.slice(0, 200), [compareFilteredLineRows])
  const compareVisibleIssueLines = useMemo(
    () => compareVisibleLineRows.filter((row) => row.status !== 'same').map((row) => row.lineNumber),
    [compareVisibleLineRows]
  )
  const compareIssueRowsForReview = useMemo(
    () => compareLineRows.filter((row) => row.status !== 'same'),
    [compareLineRows]
  )
  const compareVisibleIssueRowsForReview = useMemo(
    () => compareVisibleLineRows.filter((row) => row.status !== 'same'),
    [compareVisibleLineRows]
  )
  const compareProposalReviewSummary = useMemo(() => {
    const issueLineSet = new Set(compareIssueRowsForReview.map((row) => row.lineNumber))
    let accepted = 0
    let rejected = 0
    let assigned = 0
    for (const [lineRaw, decision] of Object.entries(compareProposalReviewDecisions)) {
      const line = Number.parseInt(lineRaw, 10)
      if (!issueLineSet.has(line)) continue
      if (decision === 'accept') accepted += 1
      if (decision === 'reject') rejected += 1
    }
    for (const line of issueLineSet) {
      const assignee = compareProposalReviewAssignments[line]
      if (typeof assignee === 'string' && assignee.trim()) {
        assigned += 1
      }
    }
    const total = issueLineSet.size
    return {
      total,
      accepted,
      rejected,
      pending: Math.max(0, total - accepted - rejected),
      assigned,
      unassigned: Math.max(0, total - assigned),
    }
  }, [compareIssueRowsForReview, compareProposalReviewDecisions, compareProposalReviewAssignments])
  const compareProposalReviewRowsPreview = useMemo(
    () => compareVisibleIssueRowsForReview.slice(0, 24),
    [compareVisibleIssueRowsForReview]
  )
  const compareIssueCursor = useMemo(() => {
    if (!compareVisibleIssueLines.length) return null
    if (compareActiveIssueLine === null || !compareVisibleIssueLines.includes(compareActiveIssueLine)) {
      return { index: 1, total: compareVisibleIssueLines.length, lineNumber: compareVisibleIssueLines[0] }
    }
    return {
      index: compareVisibleIssueLines.indexOf(compareActiveIssueLine) + 1,
      total: compareVisibleIssueLines.length,
      lineNumber: compareActiveIssueLine,
    }
  }, [compareActiveIssueLine, compareVisibleIssueLines])
  const compareLineRowsTruncated = compareFilteredLineRows.length > compareVisibleLineRows.length
  useEffect(() => {
    setCompareProposalReviewDecisions({})
    setCompareProposalReviewAssignments({})
    setCompareProposalReviewDefaultAssignee('')
    setCompareProposalReviewLastAppliedAt(null)
  }, [routeProjectId, compareDocument?.id])
  const compareRouteContextLineStart = compareRouteContext.compareIssueFirstLine
  const compareRouteContextLineEnd = compareRouteContext.compareIssueLastLine
    && compareRouteContext.compareIssueLastLine >= (compareRouteContext.compareIssueFirstLine || 0)
    ? compareRouteContext.compareIssueLastLine
    : null
  const compareRouteContextLineLabel = useMemo(() => {
    if (!compareRouteContextLineStart) return null
    if (compareRouteContextLineEnd && compareRouteContextLineEnd !== compareRouteContextLineStart) {
      return `${compareRouteContextLineStart}-${compareRouteContextLineEnd}`
    }
    return String(compareRouteContextLineStart)
  }, [compareRouteContextLineEnd, compareRouteContextLineStart])
  const compareRouteGraphDiffLabel = useMemo(() => {
    const baselineName = compareRouteContext.graphDiffBaselineSnapshotName
      || compareRouteContext.graphDiffBaselineSnapshotId
    const targetName = compareRouteContext.graphDiffTargetSnapshotName
      || compareRouteContext.graphDiffTargetSnapshotId
    if (!baselineName && !targetName) return null
    const formatSigned = (value: number | null) => {
      if (value === null) return '-'
      return value > 0 ? `+${value}` : `${value}`
    }
    return `${baselineName || 'baseline'} -> ${targetName || 'target'} (nodes ${formatSigned(compareRouteContext.graphDiffNodeDelta)}, edges ${formatSigned(compareRouteContext.graphDiffEdgeDelta)})`
  }, [
    compareRouteContext.graphDiffBaselineSnapshotId,
    compareRouteContext.graphDiffBaselineSnapshotName,
    compareRouteContext.graphDiffEdgeDelta,
    compareRouteContext.graphDiffNodeDelta,
    compareRouteContext.graphDiffTargetSnapshotId,
    compareRouteContext.graphDiffTargetSnapshotName,
  ])
  const compareIssueContextFallback = useMemo(() => {
    const different = compareIssueSummary.buckets.different
    const primaryOnly = compareIssueSummary.buckets.primary_only
    const secondaryOnly = compareIssueSummary.buckets.secondary_only
    const nonZeroKinds = ([
      different.count > 0 ? 'different' : null,
      primaryOnly.count > 0 ? 'primary_only' : null,
      secondaryOnly.count > 0 ? 'secondary_only' : null,
    ].filter(Boolean) as CrossDocumentCompareIssueKind[])
    const singleBucketKind: CrossDocumentCompareIssueKind | null = nonZeroKinds.length === 1 ? nonZeroKinds[0] : null
    const firstLineCandidates = [
      different.firstLine,
      primaryOnly.firstLine,
      secondaryOnly.firstLine,
    ].filter((value): value is number => typeof value === 'number' && value > 0)
    const lastLineCandidates = [
      different.lastLine,
      primaryOnly.lastLine,
      secondaryOnly.lastLine,
    ].filter((value): value is number => typeof value === 'number' && value > 0)
    const changedFirstLine = firstLineCandidates.length ? Math.min(...firstLineCandidates) : null
    const changedLastLine = lastLineCandidates.length ? Math.max(...lastLineCandidates) : null
    if (compareLineFilter === 'different') {
      return {
        kind: 'different' as const,
        count: different.count,
        firstLine: different.firstLine,
        lastLine: different.lastLine,
      }
    }
    if (compareLineFilter === 'one_sided') {
      const oneSidedCount = primaryOnly.count + secondaryOnly.count
      const oneSidedKind: CrossDocumentCompareIssueKind | null =
        primaryOnly.count > 0 && secondaryOnly.count === 0
          ? 'primary_only'
          : secondaryOnly.count > 0 && primaryOnly.count === 0
            ? 'secondary_only'
            : null
      return {
        kind: oneSidedKind,
        count: oneSidedCount,
        firstLine: [primaryOnly.firstLine, secondaryOnly.firstLine]
          .filter((value): value is number => typeof value === 'number' && value > 0)
          .sort((a, b) => a - b)[0] ?? null,
        lastLine: [primaryOnly.lastLine, secondaryOnly.lastLine]
          .filter((value): value is number => typeof value === 'number' && value > 0)
          .sort((a, b) => b - a)[0] ?? null,
      }
    }
    if (compareLineFilter === 'changed') {
      return {
        kind: singleBucketKind,
        count: compareIssueSummary.totalIssueRows,
        firstLine: changedFirstLine,
        lastLine: changedLastLine,
      }
    }
    return null
  }, [compareIssueSummary, compareLineFilter])
  const compareContextParams = useMemo(() => {
    return buildEditorCompareContextParams({
      docId,
      compareDocumentId,
      compareDocumentName,
      firstDifferingLine: compareSummary.firstDifferingLine,
      compareLineFilterMode: compareLineFilter,
      compareIssueContext: compareIssueContextFallback,
      routeContext: compareRouteContext,
    })
  }, [
    compareDocumentId,
    compareDocumentName,
    compareIssueContextFallback,
    compareLineFilter,
    compareRouteContext,
    compareSummary.firstDifferingLine,
    docId,
  ])
  const buildCompareIssueContextParams = useCallback((kind: CrossDocumentCompareIssueKind) => {
    const bucket = compareIssueSummary.buckets[kind]
    return buildEditorIssueBucketContextParams({
      docId,
      compareDocumentId,
      compareDocumentName,
      firstDifferingLine: compareSummary.firstDifferingLine,
      compareLineFilterMode: compareLineFilter,
      compareIssueContext: compareIssueContextFallback,
      routeContext: compareRouteContext,
      issueKind: kind,
      issueCount: bucket.count,
      issueFirstLine: bucket.firstLine,
      issueLastLine: bucket.lastLine,
    })
  }, [
    compareDocumentId,
    compareDocumentName,
    compareIssueContextFallback,
    compareIssueSummary,
    compareLineFilter,
    compareRouteContext,
    compareSummary.firstDifferingLine,
    docId,
  ])
  const buildCompareIssueRemediationParams = useCallback((kind: CrossDocumentCompareIssueKind) => {
    const bucket = compareIssueSummary.buckets[kind]
    const attachment = compareIntegrityAttachments[kind]
    return buildEditorIssueBucketRemediationParams({
      docId,
      compareDocumentId,
      compareDocumentName,
      firstDifferingLine: compareSummary.firstDifferingLine,
      compareLineFilterMode: compareLineFilter,
      compareIssueContext: compareIssueContextFallback,
      routeContext: compareRouteContext,
      issueKind: kind,
      issueCount: bucket.count,
      issueFirstLine: bucket.firstLine,
      issueLastLine: bucket.lastLine,
      remediationRoute: resolveCompareIssueRemediationRoute(kind),
      integrityIssueKey: attachment.top?.key || null,
      integrityIssueCount: attachment.top?.count || null,
    })
  }, [
    compareDocumentId,
    compareDocumentName,
    compareIssueContextFallback,
    compareIssueSummary,
    compareIntegrityAttachments,
    compareLineFilter,
    compareRouteContext,
    compareSummary.firstDifferingLine,
    docId,
  ])
  const activeCompareIssueContext = useMemo(
    () => resolveCompareIssueContextAtLine(compareVisibleLineRows, compareActiveIssueLine),
    [compareActiveIssueLine, compareVisibleLineRows]
  )
  const activeCompareIssueRouteParams = useMemo(() => {
    if (!activeCompareIssueContext) return ''
    return buildEditorFocusedIssueContextParams({
      docId,
      compareDocumentId,
      compareDocumentName,
      firstDifferingLine: compareSummary.firstDifferingLine,
      compareLineFilterMode: compareLineFilter,
      compareIssueContext: compareIssueContextFallback,
      routeContext: compareRouteContext,
      issueKind: activeCompareIssueContext.kind,
      issueLine: activeCompareIssueContext.lineNumber,
      issueCount: 1,
    })
  }, [
    activeCompareIssueContext,
    compareDocumentId,
    compareDocumentName,
    compareIssueContextFallback,
    compareLineFilter,
    compareRouteContext,
    compareSummary.firstDifferingLine,
    docId,
  ])
  const activeCompareIssueRemediationParams = useMemo(() => {
    if (!activeCompareIssueContext) return ''
    const attachment = compareIntegrityAttachments[activeCompareIssueContext.kind]
    return buildEditorFocusedIssueRemediationParams({
      docId,
      compareDocumentId,
      compareDocumentName,
      firstDifferingLine: compareSummary.firstDifferingLine,
      compareLineFilterMode: compareLineFilter,
      compareIssueContext: compareIssueContextFallback,
      routeContext: compareRouteContext,
      issueKind: activeCompareIssueContext.kind,
      issueLine: activeCompareIssueContext.lineNumber,
      issueCount: 1,
      remediationRoute: resolveCompareIssueRemediationRoute(activeCompareIssueContext.kind),
      integrityIssueKey: attachment?.top?.key || null,
      integrityIssueCount: attachment?.top?.count || null,
    })
  }, [
    activeCompareIssueContext,
    compareDocumentId,
    compareDocumentName,
    compareIssueContextFallback,
    compareIntegrityAttachments,
