import React, { useCallback, useEffect, useRef, useState } from "react";
import { useReactiveVar } from "@apollo/client";
import { unstable_batchedUpdates } from "react-dom";
import { Button, Modal, ModalBody, ModalFooter } from "@os-legal/ui";
import { ErrorMessage } from "../../widgets/feedback";
import { useFeatureAvailability } from "../../../hooks/useFeatureAvailability";
import { GlobalWorkerOptions } from "pdfjs-dist";
import workerSrc from "pdfjs-dist/build/pdf.worker.mjs?url";
import { useUISettings } from "../../annotator/hooks/useUISettings";
import useWindowDimensions from "../../hooks/WindowDimensionHook";
import { ViewState } from "../../types";
import { toast } from "react-toastify";
import {
  useDocText,
  useSearchText,
  useTextSearchState,
} from "../../annotator/context/DocumentAtom";
import { useDocumentPermissions } from "../../annotator/context/DocumentAtom";
import { useAtom, useSetAtom } from "jotai";
import { useAnnotationSelection } from "../../annotator/context/UISettingsAtom";
import { useChatSourceState } from "../../annotator/context/ChatSourceAtom";
import {
  useCreateAnnotation,
  useCreateUrlAnnotation,
} from "../../annotator/hooks/AnnotationHooks";
import { ServerTokenAnnotation } from "../../annotator/types/annotations";
import {
  selectedRelationsAtom,
  initialZoomSetAtom,
  useAnnotationControls,
  useChatPanelWidth,
} from "../../annotator/context/UISettingsAtom";
import { useCorpusState } from "../../annotator/context/CorpusAtom";
import { pdfAnnotationsAtom } from "../../annotator/context/AnnotationAtoms";
import { useNavigate, useLocation } from "react-router-dom";
import { updateAnnotationSelectionParams } from "../../../utils/navigationUtils";
import { routingLogger } from "../../../utils/routingLogger";
import { canEditAnnotationsInCorpus } from "../../../utils/annotationPermissions";
import { selectedNoteId, selectedThreadId } from "../../../graphql/cache";
import { useAuthReady } from "../../../hooks/useAuthReady";
import { useTextSearch } from "../../annotator/hooks/useTextSearch";
import {
  useAnalysisManager,
  useAnalysisSelection,
} from "../../annotator/hooks/AnalysisHooks";

import { ContentFilters, SortOption, SidebarViewMode } from "./unified_feed";
import UnifiedKnowledgeLayer from "./layers/UnifiedKnowledgeLayer";

import {
  PANEL_WIDTH_QUARTER_PCT,
  PANEL_WIDTH_HALF_PCT,
  PANEL_WIDTH_FULL_PCT,
} from "../../../assets/configurations/constants";

// Sub-components extracted from DocumentKnowledgeBase
import { useZoomManager } from "./document_kb/useZoomManager";
import { DocumentViewer } from "./document_kb/DocumentViewer";
import { useResizeHandle } from "./document_kb/useResizeHandle";
import { useDocumentMarkdown } from "./document_kb/useDocumentMarkdown";
import { useStructuralAnnotations } from "./document_kb/useStructuralAnnotations";
import { useReferenceMentions } from "./document_kb/useReferenceMentions";
import { useJumpToRelationship } from "./document_kb/useJumpToRelationship";
import { useDocumentLoader } from "./document_kb/useDocumentLoader";
import { useContainerWidth } from "./document_kb/useContainerWidth";
import { DesktopDocumentLayout } from "./layouts/DesktopDocumentLayout";
import { MobileDocumentLayout } from "./layouts/MobileDocumentLayout";

// Setting worker path to worker bundle.
GlobalWorkerOptions.workerSrc = workerSrc;

interface DocumentKnowledgeBaseProps {
  documentId: string;
  corpusId?: string; // Now optional
  /**
   * Optional list of annotation IDs that should be selected when the modal opens.
   * When provided the component will seed `selectedAnnotationsAtom`, triggering
   * the usual scroll-to-annotation behaviour in the PDF/TXT viewers.
   */
  initialAnnotationIds?: string[];
  /**
   * Optional close handler for programmatic modal usage.
   * If not provided, uses navigate(-1) to go back in browser history.
   * @deprecated Prefer routing-based navigation over programmatic modals
   */
  onClose?: () => void;
  /**
   * When true, disables all editing capabilities and shows only view-only features.
   */
  readOnly?: boolean;
  /**
   * Show information about corpus assignment state
   */
  showCorpusInfo?: boolean;
  /**
   * Optional success message to display after corpus assignment
   */
  showSuccessMessage?: string;
}

/**
 * Renders the "Invalid Document" error modal as its own component so the
 * parent `DocumentKnowledgeBase` can short-circuit BEFORE invoking any of
 * its data/UI hooks. Calling hooks above an early return is a Rules-of-Hooks
 * violation; isolating the error path here keeps the parent's hook list
 * stable across renders for any non-empty `documentId`.
 */
const InvalidDocumentIdModal: React.FC<{ onClose?: () => void }> = ({
  onClose,
}) => {
  const navigate = useNavigate();
  const handleClose = useCallback(() => {
    if (onClose) {
      onClose();
      return;
    }
    const historyIdx = (window.history.state as { idx?: number })?.idx ?? 0;
    if (historyIdx > 0) navigate(-1);
    else navigate("/documents");
  }, [onClose, navigate]);

  return (
    <Modal open onClose={handleClose} size="sm">
      <ModalBody>
        <ErrorMessage title="Invalid Document">
          Cannot load document: Invalid document ID
        </ErrorMessage>
      </ModalBody>
      <ModalFooter>
        <Button variant="secondary" onClick={handleClose}>
          Close
        </Button>
      </ModalFooter>
    </Modal>
  );
};

const DocumentKnowledgeBase: React.FC<DocumentKnowledgeBaseProps> = ({
  documentId,
  corpusId,
  initialAnnotationIds,
  onClose,
  readOnly = false,
  showCorpusInfo,
  showSuccessMessage,
}) => {
  // Validate documentId BEFORE invoking any hooks. Returning the error modal
  // from a sibling component keeps this component's hook list stable across
  // renders (prevents Rules-of-Hooks violation when the prop transitions
  // between empty and non-empty).
  if (!documentId || documentId === "") {
    console.error(
      "DocumentKnowledgeBase: Invalid documentId provided:",
      documentId
    );
    return <InvalidDocumentIdModal onClose={onClose} />;
  }

  routingLogger.debug("[DocumentKnowledgeBase] 🎬 Component render", {
    documentId,
    corpusId,
    hasOnClose: !!onClose,
    timestamp: Date.now(),
  });

  const { width } = useWindowDimensions();
  const isMobile = width < 768;
  const { isFeatureAvailable, getFeatureStatus, hasCorpus } =
    useFeatureAvailability(corpusId);

  // Memoize UI settings config to prevent creating new object reference on every render
  const uiSettingsConfig = React.useMemo(() => ({ width }), [width]);
  const { setProgress, zoomLevel, setZoomLevel } =
    useUISettings(uiSettingsConfig);

  // Reset initial zoom flag when navigating to a different document so the
  // fit-to-width calculation in PDFPage fires again for the new document.
  const setInitialZoomSet = useSetAtom(initialZoomSetAtom);
  useEffect(() => {
    setInitialZoomSet(false);
  }, [documentId, setInitialZoomSet]);

  const navigate = useNavigate();
  const location = useLocation();

  // Track component lifecycle
  useEffect(() => {
    routingLogger.debug("[DocumentKnowledgeBase] 🟢 Component MOUNTED", {
      documentId,
      corpusId,
      pathname: location.pathname,
      search: location.search,
    });

    return () => {
      routingLogger.debug("[DocumentKnowledgeBase] 🔴 Component UNMOUNTING", {
        documentId,
        corpusId,
        pathname: location.pathname,
        search: location.search,
      });
    };
  }, []); // Empty deps - only log on actual mount/unmount

  // Handle close: use provided onClose callback or fallback using browser history
  // Following routing mantra: route components should provide onClose to make navigation decisions
  // This component should NOT read openedCorpus() to decide navigation - that causes race conditions
  const handleClose = useCallback(() => {
    // Helper to navigate back or fallback to /documents
    // Uses React Router's history index to determine if there's history to go back to
    const navigateBackOrFallback = () => {
      // React Router v6 stores history index in window.history.state.idx
      // idx = 0 means this is the first page in the session (no back history)
      // idx > 0 means there's at least one page to go back to
      const historyIdx = (window.history.state as { idx?: number })?.idx ?? 0;

      if (historyIdx > 0) {
        routingLogger.debug(
          `[DocumentKnowledgeBase] Navigating back (historyIdx=${historyIdx})`
        );
        navigate(-1);
      } else {
        routingLogger.debug(
          "[DocumentKnowledgeBase] Navigating to /documents (no history)"
        );
        navigate("/documents");
      }
    };

    try {
      const timestamp = new Date().toISOString();
      routingLogger.debug(
        `🚪 [DocumentKnowledgeBase] ════════ handleClose START ════════`
      );
      routingLogger.debug("[DocumentKnowledgeBase] Timestamp:", timestamp);
      routingLogger.debug("[DocumentKnowledgeBase] Current state:", {
        hasOnClose: !!onClose,
        documentId,
        corpusId,
        currentUrl: window.location.pathname + window.location.search,
        historyIdx: (window.history.state as { idx?: number })?.idx ?? 0,
      });

      if (onClose) {
        routingLogger.debug(
          "[DocumentKnowledgeBase] ✅ Decision: Calling provided onClose callback"
        );
        onClose();
      } else {
        console.warn(
          "[DocumentKnowledgeBase] ⚠️  Decision: No onClose callback - using browser history fallback"
        );
        navigateBackOrFallback();
      }

      routingLogger.debug(
        "[DocumentKnowledgeBase] ════════ handleClose END ════════"
      );
    } catch (error) {
      console.error("[DocumentKnowledgeBase] ❌ ERROR in handleClose:", error);
      console.error("Stack trace:", error);
      // Fallback navigation on error
      navigateBackOrFallback();
    }
  }, [onClose, navigate, documentId, corpusId]);

  // Chat panel width management
  const { mode, customWidth, setMode, setCustomWidth, minimize, restore } =
    useChatPanelWidth();

  // Calculate actual panel width based on mode
  const getPanelWidthPercentage = useCallback((): number => {
    let panelWidth: number;
    switch (mode) {
      case "quarter":
        panelWidth = PANEL_WIDTH_QUARTER_PCT;
        break;
      case "half":
        panelWidth = PANEL_WIDTH_HALF_PCT;
        break;
      case "full":
        panelWidth = PANEL_WIDTH_FULL_PCT;
        break;
      case "custom":
        panelWidth = customWidth || PANEL_WIDTH_HALF_PCT;
        break;
      default:
        panelWidth = PANEL_WIDTH_HALF_PCT;
    }
    routingLogger.debug(
      "Panel width calculation - mode:",
      mode,
      "width:",
      panelWidth
    );
    return panelWidth;
  }, [mode, customWidth]);

  // Resize drag state — see useResizeHandle for the snap/clamp logic.
  const { isDragging, handleResizeStart } = useResizeHandle({
    getPanelWidthPercentage,
    setMode,
    setCustomWidth,
  });
  const [isMinimized, setIsMinimized] = useState(false);
  const documentAreaRef = useRef<HTMLDivElement>(null);

  const [showGraph, setShowGraph] = useState(false);

  // This layer state still determines whether to show the knowledge base layout vs document layout
  const [activeLayer, setActiveLayer] = useState<"knowledge" | "document">(
    "document"
  );

  const [showRightPanel, setShowRightPanel] = useState(false);

  // Calculate floating controls offset and visibility - MEMOIZED to prevent new object on every render
  const floatingControlsState = React.useMemo(() => {
    if (isMobile || !showRightPanel || activeLayer !== "document") {
      return { offset: 0, visible: true };
    }

    const panelWidthPercent = getPanelWidthPercentage();
    // Use the tracked viewport width from useWindowDimensions so this memo
    // stays in sync with `width` in the dep array — reading window.innerWidth
    // directly would silently snapshot the wrong size when only `width`
    // (without an isMobile crossing) updates.
    const panelWidthPx = (panelWidthPercent / 100) * width;
    const remainingSpacePercent = 100 - panelWidthPercent;
    const remainingSpacePx = width - panelWidthPx;

    // Hide controls if less than 10% viewport or less than 100px remaining
    const shouldHide = remainingSpacePercent < 10 || remainingSpacePx < 100;

    return {
      offset: shouldHide ? 0 : panelWidthPx,
      visible: !shouldHide,
    };
  }, [isMobile, showRightPanel, activeLayer, mode, customWidth, width]); // Dependencies: all values that affect calculation

  // Zoom management (keyboard, wheel, pinch, auto-zoom on sidebar toggle)
  const {
    showZoomIndicator,
    autoZoomEnabled,
    setAutoZoomEnabled,
    showZoomFeedback,
  } = useZoomManager({
    zoomLevel,
    setZoomLevel,
    activeLayer,
    showRightPanel,
    isMobile,
    mode,
    customWidth,
    getPanelWidthPercentage,
  });

  // pdfAnnotations is read-only here (the loader hook owns updates).
  // We need the value for the AnalysisExtractContextBar's annotation count.
  const [pdfAnnotations] = useAtom(pdfAnnotationsAtom);

  const { canUpdateCorpus } = useCorpusState();
  const { searchText, setSearchText } = useSearchText();
  const { permissions } = useDocumentPermissions();
  const { setTextSearchState } = useTextSearchState();
  const { activeSpanLabel, setActiveSpanLabel } = useAnnotationControls();
  const { setChatSourceState } = useChatSourceState();

  // Determine if user can edit based on permissions and corpus context
  const canEdit = React.useMemo(
    () =>
      canEditAnnotationsInCorpus({
        readOnly: Boolean(readOnly),
        corpusId,
        canUpdateCorpus,
        documentPermissions: permissions,
      }),
    [readOnly, corpusId, permissions, canUpdateCorpus]
  );

  // Call the hook ONCE here
  const originalCreateAnnotationHandler = useCreateAnnotation();
  const originalCreateUrlAnnotationHandler = useCreateUrlAnnotation();

  // Conditional annotation handlers based on corpus availability
  const createAnnotationHandler = React.useCallback(
    async (annotation: ServerTokenAnnotation): Promise<void> => {
      if (!corpusId) {
        toast.info("Add document to corpus to create annotations");
        return;
      }
      await originalCreateAnnotationHandler(annotation);
    },
    [corpusId, originalCreateAnnotationHandler]
  );

  const createUrlAnnotationHandler = React.useCallback(
    async (annotation: ServerTokenAnnotation, url: string): Promise<void> => {
      if (!corpusId) {
        toast.info("Add document to corpus to create annotations");
        return;
      }
      await originalCreateUrlAnnotationHandler(annotation, url);
    },
    [corpusId, originalCreateUrlAnnotationHandler]
  );

  const { selectedAnalysis, selectedExtract } = useAnalysisSelection();
  const { selectedAnnotations, setSelectedAnnotations } =
    useAnnotationSelection();
  const [, setSelectedRelations] = useAtom(selectedRelationsAtom);

  const {
    dataCells,
    columns,
    analyses,
    extracts,
    onSelectAnalysis,
    onSelectExtract,
  } = useAnalysisManager();

  useTextSearch();

  // Initialize search state on mount only - DO NOT include setters in dependencies as they're unstable!
  useEffect(() => {
    // Batch updates to prevent multiple re-renders
    unstable_batchedUpdates(() => {
      setSearchText("");
      setTextSearchState({
        matches: [],
        selectedIndex: 0,
      });
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // Empty deps = run once on mount

  /**
   * REMOVED: useEffect that cleared analysis/extract selections on mount.
   *
   * This was causing deep link params to be stripped because:
   * 1. CentralRouteManager Phase 2 correctly sets reactive vars from URL
   * 2. DocumentKnowledgeBase mounts
   * 3. This effect called onSelectAnalysis(null) which now updates URL
   * 4. URL params get stripped!
   *
   * The routing system handles initialization:
   * - URL → CentralRouteManager Phase 2 → reactive vars
   * - Reactive vars → AnalysisHooks sync effect → Jotai atoms
   * - No manual clearing needed
   */

  /**
   * If analysis or annotation is selected, switch to document view.
   */
  useEffect(() => {
    if (selectedAnalysis || (selectedAnnotations?.length ?? 0) > 0) {
      setActiveLayer("document");
    }
  }, [selectedAnalysis, selectedAnnotations]);

  /**
   * Auto-switch to extract tab when extract is selected
   * Following routing principles: only READ selectedExtract from hook
   */
  useEffect(() => {
    if (selectedExtract) {
      // Batch updates to prevent cascade of re-renders (especially in mobile)
      unstable_batchedUpdates(() => {
        setActiveLayer("document");
        setShowRightPanel(true);
        setSidebarViewMode("extract");
        // Close floating extracts panel since results now show in sidebar
        setShowExtractsPanel(false);
      });
    }
  }, [selectedExtract]);

  /**
   * Auto-switch to analysis tab when analysis is selected
   * Following routing principles: only READ selectedAnalysis from hook
   */
  useEffect(() => {
    if (selectedAnalysis) {
      // Batch updates to prevent cascade of re-renders (especially in mobile)
      unstable_batchedUpdates(() => {
        setActiveLayer("document");
        setShowRightPanel(true);
        setSidebarViewMode("analysis");
        // Close floating analyses panel since results now show in sidebar
        setShowAnalysesPanel(false);
      });
    }
  }, [selectedAnalysis]);

  // Lazy-load structural annotations (headers/sections/paragraphs) — keep
  // them out of the main payload since large documents have thousands and
  // they're hidden by default.
  // `loading` here is the targeted deep-link fetch for structural annotations,
  // which runs after the corpus/document queries settle; OR it into the shared
  // `loading` below so a structural `?ann=<id>` deep-link shows a loader instead
  // of a premature "no longer available" flash (see MobileAnnotationDetail).
  const { loading: targetedStructuralLoading } =
    useStructuralAnnotations(documentId);

  // Wire ``?rel=<pk>`` deep-link → relation selection + scroll-to.
  // No-op when the param is absent; safe to keep mounted unconditionally.
  useJumpToRelationship();

  // Container width tracking for fit-to-width zoom.
  const { containerWidth, containerRefCallback } = useContainerWidth();

  // Document data + body loading + thread count + analysis/extract refetch.
  const authReady = useAuthReady();
  const {
    corpusData,
    documentOnlyData,
    combinedData,
    loading,
    queryError,
    refetch,
    viewState,
    threadCount,
  } = useDocumentLoader({
    documentId,
    corpusId,
    authReady,
    zoomLevel,
    setProgress,
    selectedAnalysisId: selectedAnalysis?.id ?? null,
    selectedExtractId: selectedExtract?.id ?? null,
  });

  // Auto-merge reference-mention annotations (the document's citation
  // hyperlinks) — analysis-created and therefore excluded from the default
  // load, but a reader expects citations visible without selecting an
  // analysis. Gated on the loader settling: its onCompleted REPLACES the
  // annotation state, so merging before it would be silently wiped. See
  // useReferenceMentions for the full rationale.
  useReferenceMentions(documentId, corpusId, !loading);

  // `description` is the inline markdown (canonical-CAML projection).
  const corpusMdContent = corpusData?.corpus?.description ?? null;

  const metadata = combinedData?.document ?? {
    title: "Loading...",
    fileType: "",
    creator: { id: "", slug: "" },
    created: new Date().toISOString(),
  };

  const notes = corpusId
    ? corpusData?.document?.allNotes ?? []
    : documentOnlyData?.document?.allNotes ?? [];
  const docRelationships = corpusId
    ? corpusData?.document?.allDocRelationships ?? []
    : [];

  // Auto-minimize logic
  const handleDocumentMouseEnter = useCallback(() => {
    // Desktop: no auto-collapse – user controls size fully.
    if (!isMobile) return;

    // Mobile / small-screen responsive mode: close the panel when the user
    // interacts with the document to maximise canvas real-estate.
    if (showRightPanel && !isDragging) {
      setShowRightPanel(false);
    }
  }, [showRightPanel, isDragging, isMobile, setShowRightPanel]);

  const handlePanelMouseEnter = useCallback(() => {
    // Restoration logic only relevant on desktop where we allow minimised width
    if (!isMobile && isMinimized) {
      restore();
      setIsMinimized(false);
    }
  }, [isMinimized, restore, isMobile]);

  // Reset minimized state when panel closes
  useEffect(() => {
    if (!showRightPanel) {
      setIsMinimized(false);
    }
  }, [showRightPanel]);

  // Load MD summary if available
  const { markdownContent } = useDocumentMarkdown(
    combinedData?.document?.mdSummaryFile
  );

  const [selectedNote, setSelectedNote] = useState<(typeof notes)[0] | null>(
    null
  );
  const [editingNoteId, setEditingNoteId] = useState<string | null>(null);
  const [showNewNoteModal, setShowNewNoteModal] = useState(false);

  // Unified feed state
  const [sidebarViewMode, setSidebarViewMode] =
    useState<SidebarViewMode["mode"]>("chat");
  const [feedFilters, setFeedFilters] = useState<ContentFilters>({
    contentTypes: new Set(["note", "annotation", "relationship", "search"]),
    // Note: annotationFilters and relationshipFilters are now managed via atoms
    // in useAnnotationDisplay() for consistency across all components
  });
  const [feedSortBy, setFeedSortBy] = useState<SortOption>("page");

  // Add new state for floating panels
  const [showAnalysesPanel, setShowAnalysesPanel] = useState(false);
  const [showExtractsPanel, setShowExtractsPanel] = useState(false);

  // When the chat panel takes over the right tray, ``hideDocumentTools`` on
  // ``FloatingDocumentControls`` removes the toggle FABs for the floating
  // analyses / extracts panels. Close any panel that's already open on that
  // transition so the user isn't left with a panel they can no longer dismiss
  // via the (now-hidden) FAB.
  useEffect(() => {
    if (showRightPanel && sidebarViewMode === "chat") {
      setShowAnalysesPanel(false);
      setShowExtractsPanel(false);
    }
  }, [showRightPanel, sidebarViewMode]);
  // showLoad was lifted onto chatTrayStateAtom — see ChatTray.tsx and
  // UISettingsAtom.tsx (ChatTrayPersist.showLoad). Owning it here as a
  // useState made ChatTray's mount-time setShowLoad(false) call a
  // cross-component setState during render, which React flagged as a
  // "Cannot update a component (DocumentKnowledgeBase) while rendering
  // a different component (ChatTray)" warning.
  const [pendingChatMessage, setPendingChatMessage] = useState<string>();

  // Clear pending message after passing it to ChatTray
  useEffect(() => {
    if (pendingChatMessage) {
      // Clear after a short delay to ensure ChatTray has received it
      const timer = setTimeout(() => setPendingChatMessage(undefined), 100);
      return () => clearTimeout(timer);
    }
  }, [pendingChatMessage]);

  // Auto-open sidebar when ?thread= param detected
  const threadId = useReactiveVar(selectedThreadId);
  useEffect(() => {
    if (threadId && combinedData?.document) {
      unstable_batchedUpdates(() => {
        setShowRightPanel(true);
        setMode("half"); // 50% width to keep document visible
        setSidebarViewMode("discussions");
      });
    }
  }, [threadId, combinedData?.document]);

  // Auto-open the note detail modal when ?note= deep-link is present.
  const deepLinkedNoteId = useReactiveVar(selectedNoteId);
  useEffect(() => {
    // Wait until the document query has resolved before deciding the
    // ?note=<id> param is unresolvable — `notes` is empty during the
    // loading window too, and clearing then would race the load.
    if (!deepLinkedNoteId || !combinedData?.document) return;
    const target = notes.find((n) => n.id === deepLinkedNoteId);
    if (target) setSelectedNote(target);
    // Clear regardless of match: once the document is loaded, a missing
    // target means the note is inaccessible, deleted, or the ID is stale —
    // leaving the var set would pin ?note=<id> in the URL forever via
    // CentralRouteManager.
    selectedNoteId(null);
  }, [deepLinkedNoteId, combinedData?.document, notes]);

  // The main viewer content:
  const viewerContent = (
    <DocumentViewer
      fileType={metadata.fileType ?? ""}
      viewState={viewState}
      canEdit={canEdit}
      containerWidth={containerWidth}
      containerRefCallback={containerRefCallback}
      createAnnotationHandler={createAnnotationHandler}
      createUrlAnnotationHandler={createUrlAnnotationHandler}
    />
  );

  // Decide which content is in the center based on activeLayer
  const mainLayerContent =
    activeLayer === "knowledge" && corpusId ? (
      <UnifiedKnowledgeLayer
        documentId={documentId}
        corpusId={corpusId}
        metadata={metadata}
        parentLoading={loading}
        readOnly={readOnly}
      />
    ) : (
      <div
        id="document-layer"
        ref={documentAreaRef}
        onMouseEnter={handleDocumentMouseEnter}
        style={{
          position: "relative",
          width:
            !isMobile && showRightPanel
              ? `${100 - getPanelWidthPercentage()}%`
              : "100%",
          height: "100%",
          overflow: "hidden",
          transition: "width 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
        }}
      >
        {viewerContent}
      </div>
    );

  // Set initial state - ensure chat panel starts with proper width
  useEffect(() => {
    // Batch updates to prevent multiple re-renders
    unstable_batchedUpdates(() => {
      setShowRightPanel(false);
      setActiveLayer("document");
      // Force initial width to half
      if (mode !== "half") {
        setMode("half");
      }
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // Empty deps = run once on mount

  // Auto-show right panel with feed view when annotations are available
  // TEMPORARILY DISABLED: This auto-open behavior breaks tests that expect manual sidebar opening
  // useEffect(() => {
  //   if (
  //     corpusId &&
  //     combinedData?.document?.allAnnotations &&
  //     combinedData.document.allAnnotations.length > 0
  //   ) {
  //     setShowRightPanel(true);
  //     setSidebarViewMode("feed");
  //   }
  // }, [corpusId, combinedData?.document?.allAnnotations, setSidebarViewMode]);

  /* ------------------------------------------------------------------ */
  /* NOTE: Initial annotation seeding removed - incompatible with router-based state
   *
   * With router-based architecture, annotation selection is controlled by URL params.
   * For route-based usage: URL already contains ?ann=... via CentralRouteManager
   * For modal usage: This needs refactoring - calling setSelectedAnnotations navigates
   * the URL which is wrong for modals. Future fix should use a different approach for
   * modal contexts (e.g., navigate to URL when opening modal, restore on close).
   *
   * NOTE(deferred): Modal annotation seeding needs an approach that doesn't conflict
   * with router-based state — e.g. navigate to URL when opening modal, restore on close.
   */

  /* ------------------------------------------------------ */
  /*  Cleanup on unmount                                    */
  /* ------------------------------------------------------ */
  useEffect(() => {
    return () => {
      // DO NOT call setSelectedAnnotations([]) - it navigates the URL during unmount!
      // CentralRouteManager handles clearing state when routes change.

      // Clear selected relationships (local Jotai atom, not URL-driven)
      setSelectedRelations([]);
    };
  }, [setSelectedRelations]);

  const [selectedSummaryContent, setSelectedSummaryContent] = useState<
    string | null
  >(null);

  const [showAddToCorpusModal, setShowAddToCorpusModal] = useState(false);

  // Handler to clear analysis/extract selection via URL update
  // Following routing system principles: Component → URL → CentralRouteManager → Reactive Var
  const handleClearAnalysisExtractSelection = useCallback(() => {
    updateAnnotationSelectionParams(location, navigate, {
      analysisIds: [],
      extractIds: [],
    });
    // CentralRouteManager Phase 2 will detect URL change and clear selectedAnalysesIds/selectedExtractIds

    // Close sidebar and switch back to feed view when clearing
    setShowRightPanel(false);
    setSidebarViewMode("feed");
  }, [location, navigate]);

  const layoutProps = {
    documentId,
    corpusId,
    readOnly,
    showCorpusInfo,
    showSuccessMessage,
    activeLayer,
    setActiveLayer,
    showRightPanel,
    setShowRightPanel,
    sidebarViewMode,
    setSidebarViewMode,
    showGraph,
    setShowGraph,
    selectedNote,
    setSelectedNote,
    editingNoteId,
    setEditingNoteId,
    showNewNoteModal,
    setShowNewNoteModal,
    showAddToCorpusModal,
    setShowAddToCorpusModal,
    feedFilters,
    setFeedFilters,
    feedSortBy,
    setFeedSortBy,
    showAnalysesPanel,
    setShowAnalysesPanel,
    showExtractsPanel,
    setShowExtractsPanel,
    pendingChatMessage,
    setPendingChatMessage,
    setSelectedSummaryContent,
    metadata,
    hasCorpus: Boolean(hasCorpus),
    zoomLevel,
    setZoomLevel,
    showZoomIndicator,
    showZoomFeedback,
    autoZoomEnabled,
    setAutoZoomEnabled,
    mainLayerContent,
    viewerContent,
    floatingControlsState,
    mode,
    setMode,
    isDragging,
    handleResizeStart,
    handlePanelMouseEnter,
    getPanelWidthPercentage,
    handleClose,
    handleClearAnalysisExtractSelection,
    pdfAnnotations,
    analyses,
    extracts,
    selectedAnalysis,
    selectedExtract,
    threadCount,
    dataCells,
    columns,
    notes,
    // Include the targeted structural deep-link fetch so a `?ann=<id>` link to a
    // structural annotation shows a loader until that fetch settles, not just
    // until the corpus/document queries settle.
    loading: loading || targetedStructuralLoading,
    queryError,
    corpusData,
    combinedDocumentData: combinedData?.document,
    refetch,
    corpusMdContent,
    searchText,
    canEdit,
    activeSpanLabel,
    setActiveSpanLabel,
    setChatSourceState,
  };

  return isMobile ? (
    <MobileDocumentLayout {...layoutProps} />
  ) : (
    <DesktopDocumentLayout {...layoutProps} />
  );
};

// REMOVED React.memo - was preventing proper unmounting during route transitions
// When navigating away, we need the component to unmount immediately, but React.memo
// was keeping stale instances alive briefly, causing flickering during state changes
export default DocumentKnowledgeBase;
