/**
 * @file ImageCleanupModal.tsx - Modal for performing image cleanup operations (border crop, line removal, deskew, etc.) on the currently viewed PDF page via a WebSocket connection to the RWP Companion desktop application.
 * Opens a WebSocket connection, sends the current page as a base64 PDF, receives the cleaned page back, then reloads the document in the viewer.
 * Also supports automatic operations triggered by hotkeys that open the modal and immediately perform a specific cleanup action.
 */

import { type FC, useEffect, useRef, useState } from 'react';
import { Alert, Modal, Spinner } from 'react-bootstrap';
import { RWP_COMPANION_WEBSOCKET_ADDRESS } from '../../../../constants.ts';
import {
  type AutomaticOperation,
  type CompanionRequest,
  type CompanionResponse,
  type ImageCleanupStatus,
} from '../../../../types/companionTool.ts';
import { useDocument } from '../../../../contexts/DocumentContext.tsx';
import { toast } from 'react-toastify';
import { useImageCleanupHotkeys } from '../../../../hooks/hotkeys/useImageCleanupHotkeys.ts';
import { imageCleanupButtonExists } from '../../../../utils/pdfViewerInjections.tsx';
import { UAParser } from 'ua-parser-js';
import { useTranslation } from 'react-i18next';

/**
 * Props for the ImageCleanupModal component.
 */
interface ImageCleanupModalProps {
  /** Whether the modal is currently visible */
  show: boolean;

  /** Setter to control modal visibility (used by automatic operations) */
  setShow: (show: boolean) => void;

  /** Callback to close the modal */
  onHide: () => void;

  /** Setter to preserve the current page number across document reloads */
  setPreservedPageNum: (pageNum: number) => void;
}

/**
 * Image Cleanup modal that manages the WebSocket lifecycle to the RWP Companion application.
 * Displays connection status, processing state, and error/success alerts.
 */
export const ImageCleanupModal: FC<ImageCleanupModalProps> = ({
  show,
  setShow,
  onHide,
  setPreservedPageNum,
}) => {
  const { pdfViewerRef, setSelectedDocumentContentHasChanged } = useDocument();

  // Persistent WebSocket ref across renders; managed by the show effect
  const webSocket = useRef<WebSocket>(null);
  // Tracks the current WebSocket/operation lifecycle state for UI rendering
  const [status, setStatus] = useState<
    'initial' | 'connected' | ImageCleanupStatus | 'error'
  >('initial');

  // Automatic operation state: when set, the modal opens and immediately sends this operation type
  const [automaticOperation, setAutomaticOperation] = useState<
    AutomaticOperation | undefined
  >();
  // Activates an automatic cleanup operation (e.g., from a hotkey) only if the cleanup button exists
  const activateAutomaticOperation = (operation: AutomaticOperation) => {
    if (imageCleanupButtonExists()) {
      setAutomaticOperation(operation);
      setShow(true);
    }
  };

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

  // Call custom hook for ImageCleanupModal hotkeys
  useImageCleanupHotkeys({
    isWindows: os.is('Windows'),
    activateAutomaticOperation,
  });

  // WebSocket lifecycle: opens connection when modal is shown, closes when hidden
  // On open: converts current PDF page to base64 blob and sends to companion
  // On message: receives cleaned page PDF, reloads document in viewer
  // On close (code 1006): companion app is not running, show error state
  useEffect(() => {
    if (show) {
      setStatus('initial');

      // Establish WebSocket connection to RWP Companion application
      webSocket.current = new WebSocket(RWP_COMPANION_WEBSOCKET_ADDRESS);

      // Handle response from companion: reload cleaned page into the viewer
      webSocket.current.onmessage = (event) => {
        const message: CompanionResponse = JSON.parse(event.data);
        setStatus(message.status as ImageCleanupStatus);

        // Preserve current page number so the viewer returns to the same page after reload
        setPreservedPageNum(pdfViewerRef.current?.currentPageNumber as number);

        const documentPath =
          'data:application/pdf;base64,' +
          (message as CompanionResponse).pdfBase64;
        pdfViewerRef.current?.load(documentPath, '');
        setSelectedDocumentContentHasChanged(true);

        webSocket.current?.close();
      };

      // On connection established: extract current page as blob, convert to base64, send to companion
      webSocket.current.onopen = () => {
        setStatus('connected');

        // Save PDF as blob and send to companion app
        pdfViewerRef.current
          ?.saveAsBlob()
          .then((blobData) => {
            const reader = new FileReader();

            reader.onload = () => {
              const result = reader.result as string;
              const base64 = result.split(',')[1];

              // Send cleanup request with current page index and operation mode
              webSocket.current?.send(
                JSON.stringify({
                  pdfBase64: base64,
                  pageIndex:
                    (pdfViewerRef.current?.currentPageNumber as number) - 1,
                  operationMode: 'cleanup',
                  automaticOperation: automaticOperation,
                } as CompanionRequest)
              );

              if (automaticOperation) {
                setAutomaticOperation(undefined);
              }
            };

            reader.onerror = () => {
              toast.error(t('effects.show.errors.failed_to_read'));
            };

            reader.readAsDataURL(blobData);
          })
          .catch(() => {
            toast.error(t('effects.show.errors.failed_to_save'));
          });
      };

      // Handle WebSocket close: code 1006 indicates abnormal closure (companion not running)
      webSocket.current.onclose = (event) => {
        // Handle unexpected closure (including companion not running)
        if (event.code === 1006) {
          setStatus('error');
          return;
        }

        onHide();
      };
    } else {
      // Clean up WebSocket when modal is hidden
      webSocket.current?.close();
      webSocket.current = null;
    }
  }, [show]);

  const { t } = useTranslation(undefined, {
    keyPrefix:
      'components.processing.document_editor_panel.modals.image_cleanup',
  });

  return (
    <Modal show={show} onHide={onHide} backdrop={'static'} centered>
      <Modal.Header closeButton>
        <Modal.Title>{t('modal.header.title')}</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <div>
          {t('modal.body.paragraph_text')}
          <br />
          <br />
          <i>{t('modal.body.italics_text')}</i>
        </div>
        <hr />
        {status === 'error' ? (
          <Alert variant={'danger'} className={'text-center mb-0'}>
            {t('modal.body.alerts.error')}
          </Alert>
        ) : status === 'Page updated' ? (
          <Alert variant={'success'} className={'text-center mb-0'}>
            {t('modal.body.alerts.page_updated')}
          </Alert>
        ) : (
          <div
            className={`d-flex flex-column gap-2 justify-content-center align-items-center`}
          >
            <Spinner animation={'border'} role={'status'} variant={'primary'}>
              <span className={'visually-hidden'}>
                {t('modal.body.loading.spinner_text')}...
              </span>
            </Spinner>
            <div>
              <i>
                {status === 'connected'
                  ? t('modal.body.loading.status_messages.connected')
                  : t('modal.body.loading.status_messages.default')}
              </i>
            </div>
          </div>
        )}
      </Modal.Body>
    </Modal>
  );
};
