/**
 * @file ReauthContext.tsx - Manages the reauthentication modal that appears when the user's JWT token expires during an active session.
 * Provides both an in-React hook (useReauth) and a global imperative function (showGlobalReauthModal) so that non-component code can trigger reauthentication.
 * Also listens for cross-tab session update and session refresh events dispatched by AuthContext so that reauthentication in one tab automatically dismisses the modal and refreshes data in other tabs.
 */

import {
  createContext,
  type ReactNode,
  useCallback,
  useContext,
  useEffect,
  useState,
} from 'react';
import { Button, Modal, Spinner } from 'react-bootstrap';
import { useModalObserver } from '../hooks/modals/useModalObserver.ts';
import { useTranslation } from 'react-i18next';
import { AuthForm } from '../components/AuthForm.tsx';
import i18next from 'i18next';
import {
  AUTH_SESSION_SYNC_REFRESHED_EVENT_NAME,
  AUTH_SESSION_SYNC_UPDATED_EVENT_NAME,
} from '../constants.ts';

/** Shape of the reauthentication context value provided to consumers. */
interface ReauthContextProps {
  /** Shows the reauthentication modal */
  showReauthModal: () => void;

  /** Hides the reauthentication modal */
  hideReauthModal: () => void;

  /** Whether the reauthentication modal is currently visible */
  reauthModalShown: boolean;

  /**
   * Monotonically increasing counter that increments after each successful reauthentication (local or cross-tab).
   * Components can watch this value in useEffect to re-fetch data when the session is restored.
   */
  reauthToken: number;

  /** The previous value of reauthToken, for comparison in useEffect guards */
  oldReauthToken: number;
  setOldReauthToken: (token: number) => void;
}

const ReauthContext = createContext<ReauthContextProps | undefined>(undefined);

// Module-level handler reference, set by the provider on mount
// This allows non-React code (e.g. fetchHelper interceptors) to trigger the modal
let showReauthModalHandler: (() => void) | null = null;

/**
 * Registers the modal-show callback. Called once by ReauthProvider on mount.
 *
 * @param handler Function that opens the reauthentication modal.
 */
export const registerReauthModal = (handler: () => void) => {
  showReauthModalHandler = handler;
};

/**
 * Imperatively shows the reauthentication modal from outside React components.
 * Typically invoked by the fetch helper when a 401 response is received.
 */
export const showGlobalReauthModal = () => {
  if (showReauthModalHandler) {
    showReauthModalHandler();
  }
};

/**
 * Provider component that renders the reauthentication modal and exposes show/hide controls plus a reauth token for downstream data refresh.
 * On mount, registers itself with the module-level handler so that non-React code can trigger the modal via {@link showGlobalReauthModal}.
 *
 * @param children Child ReactNodes to be rendered before modal
 */
export const ReauthProvider = ({ children }: { children: ReactNode }) => {
  // Modal visibility state
  const [reauthModalShown, setReauthModalShown] = useState(false);
  const showReauthModal = () => setReauthModalShown(true);
  const hideReauthModal = () => setReauthModalShown(false);
  const [reauthToken, setReauthToken] = useState(0);
  const [oldReauthToken, setOldReauthToken] = useState(0);

  // Register the modal handler on mount so non-React code can open the modal
  useEffect(() => {
    registerReauthModal(showReauthModal);
  }, []);

  const [showLoadingSpinner, setShowLoadingSpinner] = useState(false);

  /**
   * Increments the reauth token.
   * Used both after local reauthentication and when another tab broadcasts a session update/refresh, so all tabs know to re-fetch data.
   */
  const incrementReauthToken = useCallback(() => {
    setReauthToken((prev) => {
      setOldReauthToken(prev);
      return prev + 1;
    });
  }, []);

  // Listen for cross-tab session events dispatched by AuthContext
  // SESSION_UPDATED: another tab logged in (dismiss modal + bump token)
  // SESSION_REFRESHED: another tab refreshed the JWT (bump token only)
  useEffect(() => {
    const handleSessionUpdated = () => {
      incrementReauthToken();
      hideReauthModal();
    };

    const handleSessionRefreshed = () => {
      incrementReauthToken();
    };

    window.addEventListener(
      AUTH_SESSION_SYNC_UPDATED_EVENT_NAME,
      handleSessionUpdated
    );
    window.addEventListener(
      AUTH_SESSION_SYNC_REFRESHED_EVENT_NAME,
      handleSessionRefreshed
    );

    return () => {
      window.removeEventListener(
        AUTH_SESSION_SYNC_UPDATED_EVENT_NAME,
        handleSessionUpdated
      );
      window.removeEventListener(
        AUTH_SESSION_SYNC_REFRESHED_EVENT_NAME,
        handleSessionRefreshed
      );
    };
  }, [incrementReauthToken]);

  /**
   * Callback invoked after the user successfully reauthenticates via the modal's AuthForm.
   * Bumps the reauth token so downstream components re-fetch their data, then closes the modal.
   */
  const handleAfterLogin = () => {
    // Trigger update for useEffect subscribers
    if (reauthToken) {
      setOldReauthToken(reauthToken);
    }
    setReauthToken((token) => token + 1);
    hideReauthModal();
  };

  // Track how many modals are open so the backdrop can be adjusted
  const activeModalCount = useModalObserver();

  const { t } = useTranslation(undefined, {
    keyPrefix: 'contexts.reauthentication_context',
  });

  return (
    <ReauthContext.Provider
      value={{
        showReauthModal: showReauthModal,
        hideReauthModal: hideReauthModal,
        reauthModalShown: reauthModalShown,
        reauthToken: reauthToken,
        oldReauthToken: oldReauthToken,
        setOldReauthToken: setOldReauthToken,
      }}
    >
      {children}

      {/* Reauthentication modal */}
      <Modal
        show={reauthModalShown}
        onHide={hideReauthModal}
        backdrop={activeModalCount > 1 ? false : 'static'}
      >
        <Modal.Header closeButton>{t('modal.title')}</Modal.Header>
        <AuthForm
          loginThen={handleAfterLogin}
          buttons={{
            login: {
              element: (
                <Button
                  type={'submit'}
                  variant={'primary'}
                  disabled={showLoadingSpinner}
                >
                  {showLoadingSpinner ? (
                    <>
                      <Spinner
                        as={'span'}
                        animation={'border'}
                        size={'sm'}
                        role={'status'}
                        aria-hidden={'true'}
                        className={'me-2'}
                      />
                      {i18next.t(
                        'components.auth_form.buttons.log_in.logging_in_text'
                      )}
                      ...
                    </>
                  ) : (
                    <>
                      {i18next.t('components.auth_form.buttons.log_in.default')}
                    </>
                  )}
                </Button>
              ),
              setShowLoadingSpinner: setShowLoadingSpinner,
            },
            cancel: {
              element: (
                <Button variant={'secondary'} onClick={hideReauthModal}>
                  {i18next.t('components.auth_form.buttons.cancel.default')}
                </Button>
              ),
            },
          }}
          modalProps={{
            subtitle: t('modal.body.subtitle'),
          }}
        />
      </Modal>
    </ReauthContext.Provider>
  );
};

/**
 * Hook to access the ReauthContext from within a React component. Must be called within a ReauthProvider.
 *
 * @returns The reauth modal controls and reauth token state
 * @throws Error if called outside a ReauthProvider
 */
export const useReauth = () => {
  const context = useContext(ReauthContext);
  if (!context) {
    throw new Error('useReauth must be used within ReauthProvider');
  }
  return context;
};
