/**
 * @file DocumentEditorPanel.tsx - Main document editor panel wrapping the Syncfusion PDF viewer.
 *
 * Handles the full document lifecycle:
 * - Retrieval from the API (as base64)
 * - Loading into the viewer
 * - Tracking annotation/content changes
 * - Saving back to the API
 * - Managing page-level actions
 *
 * Injects custom toolbar buttons (stamps, scan insert, image cleanup) into the PDF viewer toolbar.
 * Supports auto-save when the saveWorkItem signal is triggered, page number preservation across reloads, and keyboard event interception to prevent hotkey conflicts with the PDF viewer.
 * Also renders the PageActionDropdowns portal, ImageCleanupModal, and ScanInsertModal.
 */

import { type FC, type RefObject, useEffect, useState } from 'react';
import { ContentCard } from '../../ContentCard.tsx';
import {
  type DocumentNode,
  type FolderNode,
} from '../DetailsFolderPanel/sections/FolderSection/FolderTree.tsx';
import { toast } from 'react-toastify';
import {
  Inject,
  Annotation,
  type AnnotationAddEventArgs,
  type AnnotationToolbarItem,
  FormFields,
  LinkAnnotation,
  Magnification,
  Navigation,
  PdfViewerComponent,
  TextSearch,
  TextSelection,
  ThumbnailView,
  Toolbar,
  type ToolbarItem,
} from '@syncfusion/ej2-react-pdfviewer';
import { useReauth } from '../../../contexts/ReauthContext.tsx';
import { useProcessPermissions } from '../../../contexts/ProcessPermissionsContext.tsx';
import { AtAllowRights } from '../../../types/atAllowRights.ts';
import { Browser } from '@syncfusion/ej2-base';
import { useAuth } from '../../../contexts/AuthContext.tsx';
import { Spinner } from 'react-bootstrap';
import { useWorkItem } from '../../../contexts/WorkItemContext.tsx';
import { useDocument } from '../../../contexts/DocumentContext.tsx';
import { useProcessingUI } from '../../../contexts/ProcessingUIContext.tsx';
import { cloneDeep } from 'lodash';
import {
  type CustomStampAnnotation,
  generateSignatureAnnotation,
} from '../../../utils/stampAnnotations.ts';
import { ImageCleanupModal } from './modals/ImageCleanupModal.tsx';
import { ScanInsertModal } from './modals/ScanInsertModal.tsx';
import type { ScanInsertMode } from '../../../types/companionTool.ts';
import { initializePageIndicesMap } from '../../../utils/pageIndexing.ts';
import {
  injectToolbarButtons,
  removeToolbarButtons,
} from '../../../utils/pdfViewerInjections.tsx';
import {
  type RetrieveAndSaveDocumentProps,
  retrieveDocument,
  saveDocument,
} from '../../../utils/retrieveAndSaveDocument.ts';
import { Key } from 'ts-key-enum';
import { type PageLevelActionsProps } from '../../../utils/pageLevelActions.ts';
import { useDocumentEditorHotkeys } from '../../../hooks/hotkeys/useDocumentEditorHotkeys.ts';
import { UAParser } from 'ua-parser-js';
import { PageActionDropdowns } from './portals/PageActionDropdowns.tsx';
import { useTranslation } from 'react-i18next';
import { L10n } from '@syncfusion/ej2-base';
import uk from '../../../i18next/uk/pdfViewer.json';

/**
 * Document editor component that allows users to edit a work item document (a PDF).
 * Wraps the Syncfusion PdfViewerComponent with custom toolbar injection, annotation tracking, auto-save support, and companion tool modals (scan insert, image cleanup).
 */
export const DocumentEditorPanel: FC = () => {
  const { username } = useAuth();

  // Used to trigger data refresh
  const { reauthToken, oldReauthToken, setOldReauthToken } = useReauth();

  // Required contexts for work item processing
  const { process, workItem, documents, setDocuments } = useWorkItem();
  const {
    selectedDocumentId,
    setSelectedDocumentId,
    oldSelectedDocumentId,
    setOldSelectedDocumentId,
    pdfViewerRef,
    setDocumentIsLoading,
    setDocumentActionButtonsDisabled,
    setDeleteDocumentButtonDisabled,
    selectedDocumentContentHasChanged,
    setSelectedDocumentContentHasChanged,
    thumbnailImagesVisible,
  } = useDocument();
  const { selectedNode, saveWorkItem, setSaveWorkItem, autoSavingNextNode } =
    useProcessingUI();
  const { hasPermission } = useProcessPermissions();

  const { os } = UAParser(navigator.userAgent);

  // Sync selectedDocumentId with the currently selected tree node
  useEffect(() => {
    if ((selectedNode as DocumentNode)?.documentId !== undefined) {
      const document = documents.find(
        (_document) =>
          _document.documentId === (selectedNode as DocumentNode).documentId
      );
      if (document && document.documentId !== selectedDocumentId) {
        setSelectedDocumentContentHasChanged(false);
        setOldSelectedDocumentId(selectedDocumentId);
        setSelectedDocumentId(document.documentId);
      }
    }
  }, [selectedNode]);

  // State management for document content to be displayed in editor
  const [selectedDocumentContent, setSelectedDocumentContent] = useState<
    string | undefined
  >(undefined);
  useEffect(() => {
    if (selectedDocumentId !== undefined) {
      retrieveDocument(retrieveAndSaveDocumentProps, selectedDocumentId).catch(
        () => {
          toast.error(t('effects.retrieve_document.errors.failed_to_retrieve'));
        }
      );
    }
  }, [selectedDocumentId]);

  // Keep track of page indices in last saved version of selected document
  // Used for page revert functionality
  const [originalPageIndices, setOriginalPageIndices] = useState<
    Map<number, number | undefined>
  >(new Map());
  const [shouldUpdateOriginalPageIndices, setShouldUpdateOriginalPageIndices] =
    useState(true);

  const [pdfViewerReady, setPdfViewerReady] = useState(false);
  const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
  const [preservedPageNum, setPreservedPageNum] = useState<number>();
  const [signatureAnnotation, setSignatureAnnotation] =
    useState<CustomStampAnnotation>();
  // Called when Syncfusion PDF Viewer resources (WASM/pdfium) finish loading; generates the signature stamp
  const handleResourcesLoaded = async () => {
    setSignatureAnnotation(
      await generateSignatureAnnotation(username as string)
    );
    setPdfViewerReady(true);
  };
  // Called after a document finishes loading in the viewer; initializes annotation tracking, page index mapping, and injects custom toolbar buttons
  const handleDocumentLoad = (forceUpdateOriginalPageIndices = false) => {
    if (pdfViewerRef.current) {
      // Deep clone existing annotations so new annotations can be detected by comparison
      const annotations = pdfViewerRef.current.annotationCollection.map(
        (annotation) => cloneDeep(annotation)
      );
      // Override annotationAdd event to only flag new annotations (not pre-existing ones) as changes
      pdfViewerRef.current.annotationAdd = (args: AnnotationAddEventArgs) => {
        const match = annotations.find(
          (annotation) => annotation.annotationId === args.annotationId
        );
        if (!match) {
          handleDocumentChange();
        }
      };

      if (shouldUpdateOriginalPageIndices || forceUpdateOriginalPageIndices) {
        // Populate original page indices map
        setOriginalPageIndices(
          initializePageIndicesMap(pdfViewerRef.current.pageCount)
        );
        setShouldUpdateOriginalPageIndices(false);
      }

      injectToolbarButtons({
        pdfViewerRef: pdfViewerRef as RefObject<PdfViewerComponent>,
        isDevice: Browser.isDevice,
        isWindows: os.is('Windows'),
        signatureAnnotation: signatureAnnotation as CustomStampAnnotation,
        username: username as string,
        hasEditPermission: hasPermission(AtAllowRights.EditDocument),
        retrieveAndSaveDocumentProps,
        saveDocument,
        showScanInsertModal,
        showImageCleanupModal,
      });
      setDocumentActionButtonsDisabled(false);
      setDeleteDocumentButtonDisabled(false);
    }
  };
  const handleDocumentLoadFailed = () => {
    setDeleteDocumentButtonDisabled(false);
  };
  const handleDocumentUnload = () => {
    removeToolbarButtons();
    setDocumentActionButtonsDisabled(true);
    setDeleteDocumentButtonDisabled(true);
  };
  const handleDocumentChange = () => {
    setSelectedDocumentContentHasChanged(true);
  };
  const handleExtractTextCompleted = () => {
    if (preservedPageNum) {
      pdfViewerRef.current?.navigation.goToPage(preservedPageNum);
      setPreservedPageNum(undefined);
    }
  };

  const [justSavedDocument, setJustSavedDocument] = useState(false);
  const [forceViewerLoad, setForceViewerLoad] = useState(0);
  // Load documents when appropriate
  useEffect(() => {
    if (!selectedDocumentContent || !pdfViewerRef.current) {
      return;
    } else if (justSavedDocument) {
      // Don't reload if document content was just saved
      setJustSavedDocument(false);
      handleDocumentLoad(true);
      return;
    }

    const document = documents.find(
      (_document) => _document.documentId === selectedDocumentId
    );
    const documentPath =
      'data:application/pdf;base64,' + selectedDocumentContent;

    // Track old page number
    const oldViewerPageNum = pdfViewerRef.current.currentPageNumber;
    const oldViewerFileName = pdfViewerRef.current.fileName;
    const newViewerFileName =
      document?.documentId + '-' + document?.documentName;

    if (!hasLoadedOnce) {
      // First load waits for resourcesLoaded
      if (pdfViewerReady) {
        pdfViewerRef.current.load(documentPath, '');
        setHasLoadedOnce(true);
      }
    } else {
      // All subsequent loads load immediately
      pdfViewerRef.current.load(documentPath, '');
    }

    // Update viewer properties
    pdfViewerRef.current.fileName = newViewerFileName;
    pdfViewerRef.current.downloadFileName = document?.documentName as string;
    pdfViewerRef.current.interactionMode = 'Pan';
    pdfViewerRef.current.isAnnotationToolbarOpen = hasPermission(
      AtAllowRights.EditDocument
    );
    pdfViewerRef.current.isThumbnailViewOpen = true;

    if (oldViewerFileName === newViewerFileName) {
      setPreservedPageNum(oldViewerPageNum);
    }
    // Trigger page indices tracking update
    setShouldUpdateOriginalPageIndices(true);
  }, [selectedDocumentContent, pdfViewerReady, forceViewerLoad]);

  // State variables and functions for "Scan Insert" modal (not shared by rest of processing UI)
  const [scanInsertModalShown, setScanInsertModalShown] = useState(false);
  const showScanInsertModal = (mode: ScanInsertMode) => {
    setScanInsertMode(mode);
    setScanInsertModalShown(true);
  };
  const hideScanInsertModal = () => setScanInsertModalShown(false);
  const [scanInsertMode, setScanInsertMode] = useState<ScanInsertMode>('after');

  // State variables and functions for "Image Cleanup" modal (not shared by rest of processing UI)
  const [imageCleanupModalShown, setImageCleanupModalShown] = useState(false);
  const showImageCleanupModal = () => setImageCleanupModalShown(true);
  const hideImageCleanupModal = () => setImageCleanupModalShown(false);

  // Listen for saveWorkItem trigger and save document content so work item can close
  useEffect(() => {
    if (
      saveWorkItem &&
      selectedDocumentContentHasChanged &&
      (autoSavingNextNode as FolderNode)?.folderId === undefined
    ) {
      saveDocument(retrieveAndSaveDocumentProps);
    } else if ((autoSavingNextNode as FolderNode)?.folderId) {
      // Document content doesn't need to be auto-saved when switching to the folder node
      setSaveWorkItem(false);
    }
  }, [saveWorkItem]);

  // Retrieve and save document props
  const retrieveAndSaveDocumentProps: RetrieveAndSaveDocumentProps = {
    process,
    workItem,
    documents,
    setDocuments,
    pdfViewerRef: pdfViewerRef as RefObject<PdfViewerComponent>,
    hasLoadedOnce,
    setForceViewerLoad,
    selectedDocumentId,
    selectedDocumentContent,
    setSelectedDocumentContent,
    setSelectedDocumentContentHasChanged,
    setOldSelectedDocumentId,
    setDocumentIsLoading,
    setDocumentActionButtonsDisabled,
    setDeleteDocumentButtonDisabled,
    setJustSavedDocument,
  };
  // Page-level action props
  const pageLevelActionProps: PageLevelActionsProps = {
    process,
    workItem,
    documents,
    setDocuments,
    selectedDocumentId,
    selectedDocumentContent,
    setSelectedDocumentContentHasChanged,
    retrieveAndSaveDocumentProps,
    saveDocument,
    pdfViewerRef: pdfViewerRef as RefObject<PdfViewerComponent>,
    originalPageIndices,
    setOriginalPageIndices,
    setPreservedPageNum,
    setDocumentIsLoading,
    setDocumentActionButtonsDisabled,
    setDeleteDocumentButtonDisabled,
  };

  // Call custom hook for DocumentEditorPanel hotkeys
  useDocumentEditorHotkeys({
    pdfViewerRef: pdfViewerRef as RefObject<PdfViewerComponent>,
    thumbnailImagesVisible,
    documents,
    retrieveAndSaveDocumentProps,
    pageLevelActionProps,
    os,
  });

  // Intercept keydown events inside the Syncfusion PDF Viewer to prevent hotkey conflicts
  // Certain Ctrl+key combinations (save, arrow navigation) are captured, blocked within the viewer, and re-dispatched on document.body so application-level hotkeys still fire
  useEffect(() => {
    const viewerElement = document.getElementById('pdf-viewer');
    const viewerDivElement = viewerElement as HTMLDivElement;

    // Blocks a keyboard event within the PDF viewer, then re-dispatches it from document.body
    const refireEvent = (event: KeyboardEvent) => {
      event.preventDefault();
      event.stopPropagation();

      const newEvent = new KeyboardEvent(event.type, event);

      // Focus on body (anywhere but PDF viewer element) and dispatch new event
      document.body.focus();
      document.body.dispatchEvent(newEvent);
    };

    // keydown handler for PDF viewer element
    const handleKeyDown = (event: KeyboardEvent) => {
      if (!viewerElement || !viewerElement.contains(document.activeElement)) {
        return;
      }

      const conflictedKeys = [
        'KeyS',
        Key.ArrowUp,
        Key.ArrowDown,
        Key.ArrowLeft,
        Key.ArrowRight,
      ].map((key) => key.toLowerCase());
      if (event.ctrlKey && conflictedKeys.includes(event.code.toLowerCase())) {
        refireEvent(event);
      }
    };

    viewerDivElement.addEventListener('keydown', handleKeyDown, true); // Capture
    return () => viewerDivElement.removeEventListener('keydown', handleKeyDown);
  }, []);

  // Re-retrieve document content after a reauthentication token refresh
  useEffect(() => {
    const tokenChanged = reauthToken !== oldReauthToken;

    if (
      tokenChanged &&
      selectedDocumentId &&
      selectedDocumentId !== oldSelectedDocumentId &&
      !selectedDocumentContentHasChanged
    ) {
      retrieveDocument(
        retrieveAndSaveDocumentProps,
        selectedDocumentId
      ).finally(() => {
        if (tokenChanged) {
          setOldReauthToken(reauthToken);
        }
      });
    }
  }, [reauthToken, oldReauthToken, setOldReauthToken]);

  const { i18n, t } = useTranslation(undefined, {
    keyPrefix: 'components.processing.document_editor_panel',
  });
  L10n.load({
    uk,
  });

  return (
    <ContentCard padding={false} fillPage>
      <LoadingSpinner />

      <PdfViewerComponent
        ref={pdfViewerRef}
        id={'pdf-viewer'}
        resourcesLoaded={handleResourcesLoaded}
        documentLoad={() => handleDocumentLoad()}
        documentLoadFailed={handleDocumentLoadFailed}
        documentUnload={handleDocumentUnload}
        resourceUrl={window.location.origin + '/assets/ej2-pdfviewer-lib'}
        className={'h-100'}
        enableBookmark={false}
        enableBookmarkStyles={false}
        enableCommentPanel={false}
        enableFormDesigner={false}
        enableFormDesignerToolbar={false}
        enableFormFields={true}
        enableFormFieldsValidation={false}
        enableHandwrittenSignature={false}
        enableInkAnnotation={false}
        enablePrint={false}
        enableAnnotation={hasPermission(AtAllowRights.EditDocument)}
        isAnnotationToolbarOpen={hasPermission(AtAllowRights.EditDocument)}
        hyperlinkOpenState={'NewTab'}
        toolbarSettings={{
          showTooltip: true,
          toolbarItems: [
            'UndoRedoTool' as ToolbarItem,
            'PageNavigationTool' as ToolbarItem,
            'MagnificationTool' as ToolbarItem,
            'PanTool' as ToolbarItem,
            'SelectionTool' as ToolbarItem,
            'AnnotationEditTool' as ToolbarItem,
            'FreeTextAnnotationOption' as ToolbarItem,
            'ShapeAnnotationOption' as ToolbarItem,
            'SearchOption' as ToolbarItem,
          ],
          annotationToolbarItems: [
            'ColorEditTool' as AnnotationToolbarItem,
            'OpacityEditTool' as AnnotationToolbarItem,
            'AnnotationDeleteTool' as AnnotationToolbarItem,
            'ShapeTool' as AnnotationToolbarItem,
            'StrokeColorEditTool' as AnnotationToolbarItem,
            'ThicknessEditTool' as AnnotationToolbarItem,
            'FreeTextAnnotationTool' as AnnotationToolbarItem,
            'FontFamilyAnnotationTool' as AnnotationToolbarItem,
            'FontSizeAnnotationTool' as AnnotationToolbarItem,
            'FontStylesAnnotationTool' as AnnotationToolbarItem,
            'FontAlignAnnotationTool' as AnnotationToolbarItem,
            'FontColorAnnotationTool' as AnnotationToolbarItem,
          ],
        }}
        annotationMove={handleDocumentChange}
        annotationPropertiesChange={handleDocumentChange}
        annotationRemove={handleDocumentChange}
        annotationResize={handleDocumentChange}
        annotationSettings={{
          author: username as string,
        }}
        rectangleSettings={{
          opacity: 0.25,
          fillColor: 'yellow',
          strokeColor: 'black',
        }}
        extractTextCompleted={handleExtractTextCompleted}
        locale={i18n.resolvedLanguage}
      >
        <Inject
          services={[
            Toolbar,
            Magnification,
            Navigation,
            Annotation,
            LinkAnnotation,
            ThumbnailView,
            TextSelection,
            TextSearch,
            FormFields,
          ]}
        />
      </PdfViewerComponent>

      <PageActionDropdowns
        props={pageLevelActionProps}
        isDevice={Browser.isDevice}
        hasEditPermission={hasPermission(AtAllowRights.EditDocument)}
        hasAttachPermission={hasPermission(AtAllowRights.AttachDocument)}
      />

      <ImageCleanupModal
        show={imageCleanupModalShown}
        setShow={setImageCleanupModalShown}
        onHide={hideImageCleanupModal}
        setPreservedPageNum={setPreservedPageNum}
      />

      <ScanInsertModal
        show={scanInsertModalShown}
        setShow={setScanInsertModalShown}
        onHide={hideScanInsertModal}
        mode={scanInsertMode}
        setPreservedPageNum={setPreservedPageNum}
        originalPageIndices={originalPageIndices}
        setOriginalPageIndices={setOriginalPageIndices}
      />
    </ContentCard>
  );
};

/**
 * Full-screen loading spinner overlay shown while document content is being retrieved from the API.
 */
const LoadingSpinner = () => {
  const { t } = useTranslation(undefined, {
    keyPrefix: 'components.processing.document_editor_panel.loading_spinner',
  });

  const { documentIsLoading } = useDocument();
  return (
    <div
      className={`position-absolute top-0 start-0 w-100 h-100 d-flex flex-column gap-2 justify-content-center align-items-center bg-white pdf-viewer-loading-spinner ${documentIsLoading ? '' : 'd-none'}`}
    >
      <Spinner animation={'border'} role={'status'} variant={'primary'}>
        <span className={'visually-hidden'}>{t('spinner.default')}...</span>
      </Spinner>
      <div>{t('text')}...</div>
    </div>
  );
};
