import React, {
  createContext,
  useContext,
  useState,
  useMemo,
  useEffect,
  ReactNode,
  useRef,
  useCallback,
} from "react";
import { useNavigation } from "@app/contexts/NavigationContext";
import { useFileState } from "@app/contexts/FileContext";
import { isStirlingFile } from "@app/types/fileContext";
import type { FileId } from "@app/types/file";
import { enforceExportPolicies } from "@app/services/policyExport";
import { useTranslation } from "react-i18next";
import { alert } from "@app/components/toast";
import {
  preferencesService,
  type PdfRenderMode,
} from "@app/services/preferencesService";
import {
  createViewerActions,
  ScrollActions,
  ZoomActions,
  PanActions,
  SelectionActions,
  SpreadActions,
  RotationActions,
  SearchActions,
  ExportActions,
  BookmarkActions,
  AttachmentActions,
  PrintActions,
} from "@app/contexts/viewer/viewerActions";
import {
  BridgeRef,
  BridgeApiMap,
  BridgeStateMap,
  BridgeKey,
  ViewerBridgeRegistry,
  createBridgeRegistry,
  registerBridge as setBridgeRef,
  ScrollState,
  ZoomState,
  PanState,
  SelectionState,
  SpreadState,
  RotationState,
  SearchState,
  ExportState,
  ThumbnailAPIWrapper,
  BookmarkState,
  AttachmentState,
  DocumentPermissionsState,
  PdfPermissionFlag,
} from "@app/contexts/viewer/viewerBridges";
import { SpreadMode } from "@embedpdf/plugin-spread/react";

function useImmediateNotifier<Args extends unknown[]>() {
  const callbacksRef = useRef(new Set<(...args: Args) => void>());

  const register = useCallback((callback: (...args: Args) => void) => {
    callbacksRef.current.add(callback);
    return () => {
      callbacksRef.current.delete(callback);
    };
  }, []);

  const trigger = useCallback((...args: Args) => {
    callbacksRef.current.forEach((cb) => {
      try {
        cb(...args);
      } catch (error) {
        console.error("Immediate callback error:", error);
      }
    });
  }, []);

  return { register, trigger };
}

/**
 * ViewerContext provides a unified interface to EmbedPDF functionality.
 *
 * Architecture:
 * - Bridges store their own state locally and register with this context
 * - Context provides read-only access to bridge state via getter functions
 * - Actions call EmbedPDF APIs directly through bridge references
 * - No circular dependencies - bridges don't call back into this context
 */
export interface ViewerContextType {
  // UI state managed by this context
  isThumbnailSidebarVisible: boolean;
  toggleThumbnailSidebar: () => void;
  isBookmarkSidebarVisible: boolean;
  toggleBookmarkSidebar: () => void;
  isAttachmentSidebarVisible: boolean;
  toggleAttachmentSidebar: () => void;
  isLayerSidebarVisible: boolean;
  toggleLayerSidebar: () => void;
  hasLayers: boolean;
  setHasLayers: (value: boolean) => void;
  isCommentsSidebarVisible: boolean;
  setCommentsSidebarVisible: (visible: boolean) => void;
  toggleCommentsSidebar: () => void;

  /** Request focus or highlight of a comment card in the sidebar (opens sidebar, then scrolls + flashes or focuses input). */
  highlightCommentRequest: {
    documentId: string;
    pageIndex: number;
    annotationId: string;
    action: "focus" | "highlight";
  } | null;
  requestCommentFocus: (
    documentId: string,
    pageIndex: number,
    annotationId: string,
    hasContent: boolean,
  ) => void;
  clearHighlightCommentRequest: () => void;

  // Search interface visibility
  isSearchInterfaceVisible: boolean;
  searchInterfaceActions: {
    open: () => void;
    close: () => void;
    toggle: () => void;
  };

  // Annotation visibility toggle
  isAnnotationsVisible: boolean;
  toggleAnnotationsVisibility: () => void;

  // Annotation/drawing mode for viewer
  isAnnotationMode: boolean;
  setAnnotationMode: (enabled: boolean) => void;

  // Active file tracking — ID is the stable source of truth; index is derived from it
  activeFileId: string | null;
  setActiveFileId: (id: string | null) => void;
  activeFileIndex: number;
  setActiveFileIndex: (index: number) => void;

  // State getters - read current state from bridges
  getScrollState: () => ScrollState;
  getZoomState: () => ZoomState;
  getPanState: () => PanState;
  getSelectionState: () => SelectionState;
  getSpreadState: () => SpreadState;
  getRotationState: () => RotationState;
  getSearchState: () => SearchState;
  getThumbnailAPI: () => ThumbnailAPIWrapper | null;
  getExportState: () => ExportState;
  getBookmarkState: () => BookmarkState;
  hasBookmarkSupport: () => boolean;
  getAttachmentState: () => AttachmentState;
  hasAttachmentSupport: () => boolean;
  getDocumentPermissions: () => DocumentPermissionsState;
  hasPermission: (flag: PdfPermissionFlag) => boolean;

  // Immediate update callbacks
  registerImmediateZoomUpdate: (
    callback: (percent: number) => void,
  ) => () => void;
  registerImmediateScrollUpdate: (
    callback: (currentPage: number, totalPages: number) => void,
  ) => () => void;
  registerImmediateSpreadUpdate: (
    callback: (mode: SpreadMode, isDualPage: boolean) => void,
  ) => () => void;
  registerImmediatePanUpdate: (
    callback: (isPanning: boolean) => void,
  ) => () => void;

  // Internal - for bridges to trigger immediate updates
  triggerImmediateScrollUpdate: (
    currentPage: number,
    totalPages: number,
  ) => void;
  triggerImmediateZoomUpdate: (zoomPercent: number) => void;
  triggerImmediateSpreadUpdate: (
    mode: SpreadMode,
    isDualPage?: boolean,
  ) => void;
  triggerImmediatePanUpdate: (isPanning: boolean) => void;

  // Action handlers - call EmbedPDF APIs directly
  scrollActions: ScrollActions;
  zoomActions: ZoomActions;
  panActions: PanActions;
  selectionActions: SelectionActions;
  spreadActions: SpreadActions;
  rotationActions: RotationActions;
  searchActions: SearchActions;
  exportActions: ExportActions;
  bookmarkActions: BookmarkActions;
  attachmentActions: AttachmentActions;
  printActions: PrintActions;

  // Bridge registration - internal use by bridges
  registerBridge: <K extends BridgeKey>(
    type: K,
    ref: BridgeRef<BridgeStateMap[K], BridgeApiMap[K]> | null,
  ) => void;

  // Save changes function - registered by EmbedPdfViewer
  applyChanges: (() => Promise<void>) | null;
  setApplyChanges: (fn: (() => Promise<void>) | null) => void;

  // PDF page color rendering mode (viewer-only, never modifies the PDF)
  pdfRenderMode: PdfRenderMode;
  cyclePdfRenderMode: () => void;
}

export const ViewerContext = createContext<ViewerContextType | null>(null);

interface ViewerProviderProps {
  children: ReactNode;
}

export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
  const { t } = useTranslation();
  // UI state - only state directly managed by this context
  const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] =
    useState(false);
  const [isBookmarkSidebarVisible, setIsBookmarkSidebarVisible] =
    useState(false);
  const [isAttachmentSidebarVisible, setIsAttachmentSidebarVisible] =
    useState(false);
  const [isLayerSidebarVisible, setIsLayerSidebarVisible] = useState(false);
  const [hasLayers, setHasLayers] = useState(false);
  const [isCommentsSidebarVisible, setIsCommentsSidebarVisible] =
    useState(false);
  const [highlightCommentRequest, setHighlightCommentRequest] = useState<{
    documentId: string;
    pageIndex: number;
    annotationId: string;
    action: "focus" | "highlight";
  } | null>(null);
  const [isSearchInterfaceVisible, setSearchInterfaceVisible] = useState(false);
  const [isAnnotationsVisible, setIsAnnotationsVisible] = useState(true);
  const [isAnnotationMode, setIsAnnotationModeState] = useState(false);
  const [activeFileId, setActiveFileId] = useState<string | null>(null);

  // activeFileIndex is derived from activeFileId so they can never desync.
  // ViewerProvider sits inside FileContextProvider so useFileState is valid here.
  const { selectors, state } = useFileState();

  // Clear activeFileId when its file is removed from the workbench.
  // Dep on state.files.ids so the effect re-runs on every add/remove.
  useEffect(() => {
    if (!activeFileId) return;
    const stillInWorkbench = state.files.ids.some(
      (id) => (id as string) === activeFileId,
    );
    if (!stillInWorkbench) setActiveFileId(null);
  }, [activeFileId, state.files.ids]);

  const activeFileIndex = useMemo(() => {
    if (!activeFileId) return 0;
    const files = selectors.getFiles();
    const idx = files.findIndex((f) => f.fileId === activeFileId);
    return idx >= 0 ? idx : 0;
  }, [activeFileId, selectors]);
  const setActiveFileIndex = useCallback(
    (index: number) => {
      const files = selectors.getFiles();
      const file = files[index];
      if (file && isStirlingFile(file)) setActiveFileId(file.fileId);
    },
    [selectors],
  );
  const [pdfRenderMode, setPdfRenderModeState] = useState<PdfRenderMode>(() =>
    preferencesService.getPreference("pdfRenderMode"),
  );

  // Get current navigation state to check if we're in sign mode
  useNavigation();

  // Bridge registry - bridges register their state and APIs here
  const bridgeRefs = useRef<ViewerBridgeRegistry>(createBridgeRegistry());

  // Apply changes function - registered by EmbedPdfViewer
  const applyChangesRef = useRef<(() => Promise<void>) | null>(null);

  const setApplyChanges = useCallback((fn: (() => Promise<void>) | null) => {
    applyChangesRef.current = fn;
  }, []);

  const applyChanges = useCallback(async () => {
    if (applyChangesRef.current) {
      await applyChangesRef.current();
    }
  }, []);

  const {
    register: registerImmediateZoomUpdate,
    trigger: triggerImmediateZoomInternal,
  } = useImmediateNotifier<[number]>();
  const {
    register: registerImmediateScrollUpdate,
    trigger: triggerImmediateScrollInternal,
  } = useImmediateNotifier<[number, number]>();
  const {
    register: registerImmediateSpreadUpdate,
    trigger: triggerImmediateSpreadInternal,
  } = useImmediateNotifier<[SpreadMode, boolean]>();
  const {
    register: registerImmediatePanUpdate,
    trigger: triggerImmediatePanInternal,
  } = useImmediateNotifier<[boolean]>();

  const triggerImmediateZoomUpdate = useCallback(
    (percent: number) => {
      triggerImmediateZoomInternal(percent);
    },
    [triggerImmediateZoomInternal],
  );

  const triggerImmediateScrollUpdate = useCallback(
    (currentPage: number, totalPages: number) => {
      triggerImmediateScrollInternal(currentPage, totalPages);
    },
    [triggerImmediateScrollInternal],
  );

  const triggerImmediateSpreadUpdate = useCallback(
    (mode: SpreadMode, isDualPage: boolean = mode !== SpreadMode.None) => {
      triggerImmediateSpreadInternal(mode, isDualPage);
    },
    [triggerImmediateSpreadInternal],
  );

  const triggerImmediatePanUpdate = useCallback(
    (isPanning: boolean) => {
      triggerImmediatePanInternal(isPanning);
    },
    [triggerImmediatePanInternal],
  );

  const registerBridge = useCallback(
    <K extends BridgeKey>(
      type: K,
      ref: BridgeRef<BridgeStateMap[K], BridgeApiMap[K]> | null,
    ) => {
      setBridgeRef(bridgeRefs.current, type, ref);
    },
    [],
  );

  const toggleThumbnailSidebar = () => {
    setIsThumbnailSidebarVisible((prev) => !prev);
  };

  const toggleBookmarkSidebar = () => {
    setIsBookmarkSidebarVisible((prev) => !prev);
  };

  const toggleAttachmentSidebar = () => {
    setIsAttachmentSidebarVisible((prev) => !prev);
  };

  const toggleLayerSidebar = () => {
    setIsLayerSidebarVisible((prev) => !prev);
  };

  const setCommentsSidebarVisible = (visible: boolean) => {
    setIsCommentsSidebarVisible(visible);
  };

  const toggleCommentsSidebar = () => {
    setIsCommentsSidebarVisible((prev) => !prev);
  };

  const requestCommentFocus = useCallback(
    (
      documentId: string,
      pageIndex: number,
      annotationId: string,
      hasContent: boolean,
    ) => {
      setIsCommentsSidebarVisible(true);
      setHighlightCommentRequest({
        documentId,
        pageIndex,
        annotationId,
        action: hasContent ? "highlight" : "focus",
      });
    },
    [],
  );

  const clearHighlightCommentRequest = useCallback(() => {
    setHighlightCommentRequest(null);
  }, []);

  const searchInterfaceActions = {
    open: () => setSearchInterfaceVisible(true),
    close: () => setSearchInterfaceVisible(false),
    toggle: () => setSearchInterfaceVisible((prev) => !prev),
  };

  const toggleAnnotationsVisibility = () => {
    setIsAnnotationsVisible((prev) => !prev);
  };

  const setAnnotationMode = (enabled: boolean) => {
    setIsAnnotationModeState(enabled);
  };

  const cyclePdfRenderMode = useCallback(() => {
    setPdfRenderModeState((prev) => {
      const next: PdfRenderMode =
        prev === "normal" ? "dark" : prev === "dark" ? "sepia" : "normal";
      preferencesService.setPreference("pdfRenderMode", next);
      return next;
    });
  }, []);

  // State getters - read from bridge refs
  const getScrollState = (): ScrollState => {
    return (
      bridgeRefs.current.scroll?.state || { currentPage: 1, totalPages: 0 }
    );
  };

  const getZoomState = (): ZoomState => {
    return (
      bridgeRefs.current.zoom?.state || { currentZoom: 1.4, zoomPercent: 140 }
    );
  };

  const getPanState = (): PanState => {
    return bridgeRefs.current.pan?.state || { isPanning: false };
  };

  const getSelectionState = (): SelectionState => {
    return bridgeRefs.current.selection?.state || { hasSelection: false };
  };

  const getSpreadState = (): SpreadState => {
    return (
      bridgeRefs.current.spread?.state || {
        spreadMode: SpreadMode.None,
        isDualPage: false,
      }
    );
  };

  const getRotationState = (): RotationState => {
    return bridgeRefs.current.rotation?.state || { rotation: 0 };
  };

  const getSearchState = (): SearchState => {
    return (
      bridgeRefs.current.search?.state || { results: null, activeIndex: 0 }
    );
  };

  const getThumbnailAPI = () => {
    return bridgeRefs.current.thumbnail?.api || null;
  };

  const getExportState = (): ExportState => {
    return bridgeRefs.current.export?.state || { canExport: false };
  };

  const getBookmarkState = (): BookmarkState => {
    return (
      bridgeRefs.current.bookmark?.state || {
        bookmarks: null,
        isLoading: false,
        error: null,
      }
    );
  };

  const hasBookmarkSupport = () => Boolean(bridgeRefs.current.bookmark);

  const getAttachmentState = (): AttachmentState => {
    return (
      bridgeRefs.current.attachment?.state || {
        attachments: null,
        isLoading: false,
        error: null,
      }
    );
  };

  const hasAttachmentSupport = () => Boolean(bridgeRefs.current.attachment);

  const getDocumentPermissions = (): DocumentPermissionsState => {
    return (
      bridgeRefs.current.permissions?.state || {
        isEncrypted: false,
        isOwnerUnlocked: false,
        permissions: PdfPermissionFlag.AllowAll,
        canPrint: true,
        canModifyContents: true,
        canCopyContents: true,
        canModifyAnnotations: true,
        canFillForms: true,
        canExtractForAccessibility: true,
        canAssembleDocument: true,
        canPrintHighQuality: true,
      }
    );
  };

  const hasPermission = (flag: PdfPermissionFlag): boolean => {
    const api = bridgeRefs.current.permissions?.api;
    if (api?.hasPermission) {
      return api.hasPermission(flag);
    }
    // Default: allow all permissions - warn in development
    if (process.env.NODE_ENV === "development") {
      console.warn(
        "[ViewerContext] Permissions API not available, defaulting to allow",
      );
    }
    return true;
  };

  // Action handlers - call APIs directly
  const {
    scrollActions,
    zoomActions,
    panActions,
    selectionActions,
    spreadActions,
    rotationActions,
    searchActions,
    exportActions,
    bookmarkActions,
    attachmentActions,
    printActions,
  } = createViewerActions({
    registry: bridgeRefs,
    getScrollState,
    getZoomState,
    triggerImmediateZoomUpdate,
  });

  // Printing is an exit path, so a "run on export" policy must enforce here too.
  // Enforce the current file through the same path export uses: when a policy
  // rewrites it, that path versions the in-editor file to the enforced output
  // and marks it enforced, so a follow-up print of the unedited result prints
  // it as-is instead of re-running the (non-idempotent) policy. Ask the user to
  // review the updated doc before printing again, rather than printing bytes
  // they haven't seen. With no active export policy this is a no-op and print
  // runs straight away.
  const printWithPolicy = useCallback(async () => {
    const file = activeFileId
      ? selectors.getFiles([activeFileId as FileId])[0]
      : undefined;
    if (!activeFileId || !file) {
      printActions.print();
      return;
    }
    const [enforced] = await enforceExportPolicies(
      [file],
      [activeFileId],
      "print",
    );
    // Original file back means no policy rewrote it (no active policy, already
    // enforced, or graceful failure fallback) — nothing new to review, print it.
    if (!enforced || enforced === file) {
      printActions.print();
      return;
    }
    alert({
      alertType: "warning",
      title: t("policies.enforcement.printPolicyAppliedTitle"),
      body: t("policies.enforcement.printPolicyAppliedBody"),
    });
  }, [activeFileId, selectors, printActions]);

  const enforcedPrintActions = useMemo<PrintActions>(
    () => ({ print: printWithPolicy }),
    [printWithPolicy],
  );

  const value: ViewerContextType = {
    // UI state
    isThumbnailSidebarVisible,
    toggleThumbnailSidebar,
    isBookmarkSidebarVisible,
    toggleBookmarkSidebar,
    isAttachmentSidebarVisible,
    toggleAttachmentSidebar,
    isLayerSidebarVisible,
    toggleLayerSidebar,
    hasLayers,
    setHasLayers,
    isCommentsSidebarVisible,
    setCommentsSidebarVisible,
    toggleCommentsSidebar,
    highlightCommentRequest,
    requestCommentFocus,
    clearHighlightCommentRequest,

    // Search interface
    isSearchInterfaceVisible,
    searchInterfaceActions,

    // Annotation controls
    isAnnotationsVisible,
    toggleAnnotationsVisibility,
    isAnnotationMode,
    setAnnotationMode,

    // Active file tracking
    activeFileId,
    setActiveFileId,
    activeFileIndex,
    setActiveFileIndex,

    // State getters
    getScrollState,
    getZoomState,
    getPanState,
    getSelectionState,
    getSpreadState,
    getRotationState,
    getSearchState,
    getThumbnailAPI,
    getExportState,
    getBookmarkState,
    hasBookmarkSupport,
    getAttachmentState,
    hasAttachmentSupport,
    getDocumentPermissions,
    hasPermission,

    // Immediate updates
    registerImmediateZoomUpdate,
    registerImmediateScrollUpdate,
    registerImmediateSpreadUpdate,
    registerImmediatePanUpdate,
    triggerImmediateScrollUpdate,
    triggerImmediateZoomUpdate,
    triggerImmediateSpreadUpdate,
    triggerImmediatePanUpdate,

    // Actions
    scrollActions,
    zoomActions,
    panActions,
    selectionActions,
    spreadActions,
    rotationActions,
    searchActions,
    exportActions,
    bookmarkActions,
    attachmentActions,
    printActions: enforcedPrintActions,

    // Bridge registration
    registerBridge,

    // Apply changes
    applyChanges,
    setApplyChanges,

    // PDF page rendering mode
    pdfRenderMode,
    cyclePdfRenderMode,
  };

  return (
    <ViewerContext.Provider value={value}>{children}</ViewerContext.Provider>
  );
};

export const useViewer = (): ViewerContextType => {
  const context = useContext(ViewerContext);
  if (!context) {
    throw new Error("useViewer must be used within a ViewerProvider");
  }
  return context;
};
