import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { usePopper } from 'react-popper';
import { noop } from '../../helpers/utils';
import { ButtonContext, TextButton } from '../Buttons';
import { Message } from '../Message';
import {
  ConfirmationMode,
  ConfirmationPlacement,
} from './ConfirmDialog.models';
import classes from './ConfirmDialog.scss';

// Popper needs useState not useRef to know when the state of the component changes. More info here: https://www.youtube.com/watch?v=cIUfTktVZRM
interface useConfirmationDelayProps {
  confirmationMode: ConfirmationMode;
  confirmation: boolean;
  setConfirmation: React.Dispatch<React.SetStateAction<boolean>>;
}

export interface ConfirmDialogProps {
  referenceElement?: HTMLElement;
  /** Dialog title default('Confirmation needed') */
  title?: string;
  /** Cancel button text default('Cancel') */
  cancelButtonText?: string;
  /** Confirm button text default('Confirm') */
  confirmButtonText?: string;
  /** Optional class */
  className?: string;
  /** Default placement can be changed with this optional property (default: 'top') */
  placement?: ConfirmationPlacement;
  /** Move along the reference element default(0) */
  offsetSkidding?: number;
  /** Distance to the reference element default(6) */
  offsetDistance?: number;
  /** Raised when the 'Cancel' button is clicked */
  onCancel?: () => void;
  /** Raised when the 'Confirm' button is clicked */
  onConfirm?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
  /** Emits on mount and un-mount */
  onConfirmOpen?: (isOpen: boolean) => void;
}

/**
 * Renders a dialog pop up. Giving the user the option to confirm or cancel an action.
 * @example
 * const [referenceElement, setReferenceElement] = useState<HTMLElement>();
 * <div ref={setReferenceElement as React.LegacyRef<HTMLDivElement>}>
 *   <ConfirmDialog
 *   referenceElement={referenceElement}
 *   onCancel={onCancelHandler}
 *   onConfirm={onConfirmHandler}
 *   />
 * </div>
 */
export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
  referenceElement,
  title = 'Confirmation needed',
  children,
  cancelButtonText = 'Cancel',
  confirmButtonText = 'Confirm',
  className = '',
  placement = 'top',
  offsetSkidding = 0,
  offsetDistance = 6,
  onCancel = noop,
  onConfirm = noop,
  onConfirmOpen,
}) => {
  useEffect(() => {
    onConfirmOpen && onConfirmOpen(true);
    return () => {
      onConfirmOpen && onConfirmOpen(false);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const [popperElement, setPopperElement] = useState<HTMLDivElement>();
  const [arrowElement, setArrowElement] = useState<HTMLDivElement>();

  const { styles, attributes } = usePopper(referenceElement, popperElement, {
    modifiers: [
      {
        name: 'arrow',
        options: { element: arrowElement },
      },
      {
        name: 'offset',
        options: {
          offset: [offsetSkidding, offsetDistance],
        },
      },
    ],
    placement,
  });

  return (
    <div
      className={clsx(classes.container, 'confirm-dialog-container', className)}
      ref={setPopperElement as React.LegacyRef<HTMLDivElement>}
      style={styles.popper}
      {...attributes.popper}
      onClick={(e) => e.stopPropagation()}
    >
      <Message
        title={title}
        type="warning"
        isBodyOpen={true}
        isBodyPositionAbsolute={false}
      >
        <div className={classes.confirmBody}>
          <div className={classes.confirmBodyText}>{getChildren(children)}</div>
          <TextButton
            className={classes.confirmButton}
            text={cancelButtonText}
            onButtonClicked={onCancel}
            dataTestId="cancel"
          />
          <TextButton
            className={classes.confirmButton}
            text={confirmButtonText}
            onButtonClicked={onConfirm}
            buttonContext={ButtonContext.Active}
            dataTestId="confirm"
          />
        </div>
      </Message>

      <div
        ref={setArrowElement as React.LegacyRef<HTMLDivElement>}
        className={classes.arrow}
      />
    </div>
  );
};

// Extracted into a separate hook for reusability in the future
export const useConfirmationDelay = ({
  confirmationMode,
  confirmation,
  setConfirmation,
}: useConfirmationDelayProps): {
  readonly isMouseOver: boolean;
  readonly setIsMouseOver: React.Dispatch<React.SetStateAction<boolean>>;
} => {
  // Remove confirmation 0.5 seconds after onMouseLeave
  const [isMouseOver, setIsMouseOver] = useState<boolean>(false);
  useEffect(() => {
    let timer: NodeJS.Timeout | undefined;
    if (confirmationMode === 'Simple') {
      // start time out after mouse leaves component
      if (confirmation && !isMouseOver) {
        if (timer === undefined) {
          timer = global.setTimeout(() => {
            setConfirmation(false);
          }, 500);
        }
        // clear the timer if mouse enters again
      } else if (timer !== undefined && confirmation && isMouseOver) {
        () => clearTimeout(timer as NodeJS.Timeout);
      }
    }
    // clear the timer after the component is unmounted
    return () => clearTimeout(timer as NodeJS.Timeout);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [confirmation, isMouseOver]);

  return { isMouseOver, setIsMouseOver } as const;
};

/**
 * If children is just a 'string', will wrap it in 'p' tags, otherwise will just return children.
 * @param child component's children
 * @returns React.ReactNode
 */
const getChildren = (child?: React.ReactNode): React.ReactNode => {
  let result: React.ReactNode;

  switch (true) {
    case child === undefined:
      result = (
        <p className={classes.defaultText}>
          The action is irreversible. Please confirm to proceed.
        </p>
      );
      break;
    case typeof child === 'string':
      result = <p className={classes.defaultText}>{child}</p>;
      break;
    default:
      result = child;
      break;
  }

  return result;
};
