import { useCallback, useState } from 'preact/hooks';
import type {
  AirgapAuth,
  DismissedViewState,
  ResponseViewState,
  InitialViewState,
  ViewState,
  ViewStateEventDetails,
  TranscendEventType,
} from '@transcend-io/airgap.js-types';
import { logger } from '../logger';
import type { HandleSetViewState } from '../types';

/**
 * Helper to determine whether a view state is closed
 *
 * @param viewState - the current view state
 * @returns a boolean whether this is closed
 */
export function isViewStateClosed(
  viewState: ViewState,
): viewState is DismissedViewState {
  const closedViewStates: ViewState[] = [
    'Hidden',
    'Closed',
    'Collapsed',
    'TCF_EU',
  ];
  return closedViewStates.includes(viewState);
}

/**
 * Helper to determine whether a view state is a ResponseViewState
 *
 * @param viewState - the current view state
 * @returns a boolean whether this is a ResponseViewState
 */
export function isResponseViewState(
  viewState: ViewState,
): viewState is ResponseViewState {
  const responseViewStates: ViewState[] = ['DoNotSellDisclosure'];
  return responseViewStates.includes(viewState);
}

/**
 * State management for consent manager view states
 *
 * @param defaultStates - the states to open/close to
 * @returns the view state and a function to switch view states
 */
export function useViewState({
  initialViewState,
  dismissedViewState,
  eventTarget,
  savedActiveElement,
  autofocus,
}: {
  /** Which state this consent manager should go to when opened */
  initialViewState: InitialViewState;
  /** Which state this consent manager should go to when closed */
  dismissedViewState: DismissedViewState;
  /** The event target on the `transcend` API, where we will dispatch view state change events */
  eventTarget: EventTarget;
  /** Element previously focused before our ui modal was opened */
  savedActiveElement: HTMLElement | null;
  /** Whether to on last focused element on reopen: on or off */
  autofocus?: string;
}): {
  /** The current view state */
  viewState: ViewState;
  /** A handler for the view state */
  handleSetViewState: HandleSetViewState;
  /** The first view state when opening the modal */
  firstSelectedViewState: ViewState | null;
  /** Airgap auth */
  auth?: AirgapAuth;
} {
  const [state, setState] = useState<{
    /** The current view state */
    current: ViewState;
    /** The previous view state - used for going back */
    previous: ViewState | null;
    /** The first view state when opening the modal */
    firstSelectedViewState: ViewState | null;
    /** Airgap auth */
    auth?: AirgapAuth;
  }>({
    current: 'Hidden',
    previous: null,
    firstSelectedViewState: null,
  });

  /**
   * When the viewState is set, update the view state and track previous state + whether the modal has (ever) been dismissed
   *
   * @param requestedViewState - the requested next view state, 'open', 'close', or 'back'
   */
  const handleSetViewState: HandleSetViewState = useCallback(
    (requestedViewState, auth, resetFirstSelectedViewState = false) => {
      switch (requestedViewState) {
        // Request to go back to the previous page
        case 'back':
          if (state.previous !== null) {
            setState({
              current: state.previous,
              previous: state.current,
              auth,
              firstSelectedViewState: state.firstSelectedViewState,
            });
          } else {
            logger.warn('Tried to go back when there is no previous state');
          }
          break;

        // Request to open the modal, to the initial view state (which depends on the user's region)
        case 'open':
          setState({
            current: initialViewState,
            previous: state.current,
            auth,
            firstSelectedViewState:
              initialViewState || state.firstSelectedViewState,
          });
          break;

        // Request to close the modal, to the closed view state (which depends on whether customer wants to display the collapse view or hide it)
        case 'close': {
          setState({
            current: dismissedViewState,
            previous: state.current,
            auth,
            firstSelectedViewState: null,
          });
          /* If the user previously had something focused before the modal popped up, then focus it.
           * Otherwise, we have to do this really janky "reset" behavior with the temporary element
           * because Chrome specifically has some weird internal state around focus order that is
           * very difficult to interact with. We create an element with maximum focus priority and
           * focus it so that when we delete it the user will be at the start of the focus order
           * just like if they had freshly loaded the page. */
          const shouldAutoFocus = autofocus !== 'off';
          if (shouldAutoFocus) {
            if (savedActiveElement !== null) {
              savedActiveElement.focus();
            } else {
              const tempInteractiveEl = document.createElement('span');
              tempInteractiveEl.tabIndex = 1;
              document.documentElement.prepend(tempInteractiveEl);
              tempInteractiveEl.focus();
              tempInteractiveEl.remove();
            }
          }
          break;
        }

        // Request to go to a specific view state, e.g. the language options page
        default:
          setState({
            current: requestedViewState,
            previous: state.current,
            auth,
            firstSelectedViewState: resetFirstSelectedViewState
              ? requestedViewState
              : state.firstSelectedViewState || requestedViewState,
          });
          break;
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [state, setState, initialViewState, dismissedViewState, autofocus],
  );

  // Now that the viewState has updated, dispatch an event on the `transcend` API / event target
  const eventDetails: ViewStateEventDetails = {
    viewState: state.current,
    previousViewState: state.previous,
  };
  const eventType: TranscendEventType = 'view-state-change';
  eventTarget.dispatchEvent(
    new CustomEvent(eventType, {
      detail: eventDetails,
    }),
  );

  return {
    viewState: state.current,
    handleSetViewState,
    auth: state.auth,
    firstSelectedViewState: state.firstSelectedViewState,
  };
}
