import React, { useRef } from "react";
import classNames from "classnames";
import FocusLock from "react-focus-lock";
import { RemoveScroll } from "react-remove-scroll";
import { Box, BoxProps } from "../Box";
import { Fixed } from "../Fixed";
import { Flex } from "../Flex";
import { Portal } from "../Portal";
import { Description } from "../Description";
import { CloseButton } from "../CloseButton";
import { Stack, StackProps } from "../Stack";
import { KEY_CODE } from "../../types";
import { useKeyPress, useOutsideClick } from "../../hooks";
import { bem } from "../../utilities/bem";
import { Tab } from "../Tab";
import { Heading } from "../Heading";

const cn = "Modal";

export enum MODAL_SIZE {
  DEFAULT = "default",
  LARGE = "large",
  FULL = "full",
}

export interface ModalProps extends BoxProps {
  /**
   * The title of the modal is placed in the modal header.
   */
  title?: string;
  /**
   * The secondary title is the same font size and weight of the title,
   * but a subdued gray and it's placed on the right side if the modal
   * header opposite of the title.
   *
   * When using the secondary title, `hideCloseButton` should be `true`
   * and then the modal should be closed another way. The use case is
   * using the modal to build a set up wizard.
   */
  secondaryTitle?: string;
  /**
   * The description is smaller text placed under the title in the modal
   * header.
   *
   * Deprecated; this prop will be removed in the next major release.
   * Place any supporting description copy within the modal body.
   */
  description?: string;
  /**
   * A boolean to control whether or not the modal is open. This is
   * useful if the modal needs to be open when the page loads.
   *
   * Generally, this value is set via the `useDisclosure` hook.
   */
  isOpen?: boolean;
  /**
   * A method to close the modal window. Generally, this function is set
   * via the `useDisclosure` hook.
   */
  onClose?: () => void;
  /**
   * The modal component uses react-focus-lock to trap the keyboard focus within
   * the modal when it's opened. This is useful so that elements under the modal
   * cannot be focused with the keyboard. This is considered good practice for
   * usability and accessibility.
   *
   * By default, FocusLock gives focus to the first focusable element, which in
   * many cases is the close button. This isn't always ideal, so the default
   * value for autoFocus is false. Enable autoFocus when the modal has no other
   * focusable elements.
   *
   * If autoFocus is not set on the modal, then you'll need to manually set
   * focus to another element. This can be done by putting `autoFocus` on the
   * element.
   */
  autoFocus?: boolean;
  /**
   * Clicking outside of the modal (on the overlay) dismisses it.
   */
  disableOutsideClick?: boolean;
  /**
   * Pressing the escape key on the keyboard dismisses the modal. This
   * is the default behavior, but it can be disabled by setting this
   * prop to `true`.
   */
  disableEscClose?: boolean;
  /**
   * If the close button in the modal header needs to be hidden, it can
   * be done so by setting this prop to `true`.
   */
  hideCloseButton?: boolean;
  /**
   * Size controls the width of the modal.
   *
   * `MODAL_SIZE` enum can be one of `DEFAULT` | `LARGE` | `FULL`
   */
  size?: MODAL_SIZE;
  /**
   * Whether or not to center the modal vertically within the viewport.
   */
  center?: boolean;
}

export interface ModalBodyProps extends BoxProps {
  /**
   * Adds padding around the modal body content.
   */
  padded?: boolean;
}

export interface ModalFooterMultiStepProps extends BoxProps {
  /**
   * The total number of steps within the multi-step flow; this will be used to
   * create the correct number of dots in the progress indicator.
   */
  steps: number;
  /**
   * The number of the current step within the multi-step flow; this will be
   * used to mark the dot representing the current step active in the progress
   * indicator.
   */
  currentStep: number;
  /**
   * Text label to describe the left-hand side button, or the previous button.
   */
  leftButtonLabel?: string;
  /**
   * Text label to describe the right-hand side button, or the next button.
   */
  rightButtonLabel?: string;
}

const ModalHeader = (props: BoxProps) => {
  const { className, ...rest } = props;
  return (
    <Flex
      className={classNames([bem(cn, { e: "header" }), className])}
      {...rest}
    />
  );
};

export const ModalBody = (props: ModalBodyProps) => {
  const { padded = true, className, ...rest } = props;
  return (
    <Box
      className={classNames([
        bem(cn, { e: "body" }),
        padded && bem(cn, { e: "body", m: "padded" }),
        className,
      ])}
      {...rest}
    />
  );
};

export const ModalTabs = (props: StackProps) => {
  const { className, ...rest } = props;
  return (
    <Tab.Group
      className={classNames([bem(cn, { e: "tabs" }), className])}
      {...rest}
    />
  );
};

export const ModalFooter = (props: BoxProps) => {
  const { className, ...rest } = props;
  return (
    <Box
      className={classNames([bem(cn, { e: "footer" }), className])}
      {...rest}
    />
  );
};

export const ModalFooterMultiStep = ({
  steps,
  currentStep,
  leftButtonLabel,
  rightButtonLabel,
  children,
  className,
  ...rest
}: ModalFooterMultiStepProps) => {
  return (
    <ModalFooter
      className={classNames([bem(cn, { e: "footer" }), className])}
      {...rest}
    >
      <Flex className={bem(cn, { e: "buttonLabels" })}>
        <Box className={bem(cn, { e: "buttonLabel", m: "left" })}>
          {leftButtonLabel}
        </Box>
        <Box className={bem(cn, { e: "buttonLabel", m: "right" })}>
          {rightButtonLabel}
        </Box>
      </Flex>

      <Box className="relative">
        {children}

        <Flex className={bem(cn, { e: "progressDots" })}>
          <Stack spacing={5}>
            {[...new Array(steps).keys()].map((dot, index) => (
              <Box
                key={dot}
                className={classNames([
                  bem(cn, { e: "progressDot" }),
                  index + 1 === currentStep &&
                    bem(cn, { e: "progressDot", m: "active" }),
                ])}
              />
            ))}
          </Stack>
        </Flex>
      </Box>
    </ModalFooter>
  );
};

/**
 * Modals are overlays that prevent users from interacting with the rest
 * of the application until a specific action is taken.
 *
 * The modal contains a portal by default, so there is no need to wrap
 * it in one in the application.
 *
 * [Skip to examples](#stories)
 */
export const Modal = (props: ModalProps) => {
  const {
    title,
    secondaryTitle,
    description,
    children,
    isOpen,
    onClose,
    autoFocus = false,
    disableOutsideClick = true, // @TODO [breaking change] change to enableOutsideClick = false
    disableEscClose = false,
    hideCloseButton = false,
    size = MODAL_SIZE.DEFAULT,
    center = false,
    className,
    ...rest
  } = props;
  const modalElement = useRef<HTMLDivElement>(null);
  const showHeader = title || secondaryTitle || !hideCloseButton;

  useKeyPress(KEY_CODE.ESC, () => {
    if (!disableEscClose && onClose) {
      onClose();
    }
  });

  useOutsideClick(modalElement, () => {
    if (!disableOutsideClick && onClose) {
      onClose();
    }
  });

  return isOpen ? (
    <Portal>
      <FocusLock autoFocus={autoFocus} returnFocus>
        <RemoveScroll>
          <Fixed className={bem(`${cn}Container`)}>
            <Box
              ref={modalElement}
              className={classNames([
                bem(cn),
                bem(cn, { m: size }),
                center && bem(cn, { m: "center" }),
                className,
              ])}
              {...rest}
            >
              {showHeader && (
                <ModalHeader>
                  {title && <Heading.H2>{title}</Heading.H2>}

                  <Stack className="flex-shrink-0" spacing={5}>
                    {secondaryTitle && (
                      <Heading.H5 className="text-gray-600">
                        {secondaryTitle}
                      </Heading.H5>
                    )}

                    {!hideCloseButton && (
                      <CloseButton
                        className={bem(cn, { e: "close" })}
                        onClick={onClose}
                      />
                    )}
                  </Stack>
                </ModalHeader>
              )}

              {description && (
                <Description className={bem(cn, { e: "description" })}>
                  {description}
                </Description>
              )}

              {children}
            </Box>
          </Fixed>
        </RemoveScroll>
      </FocusLock>
    </Portal>
  ) : null;
};

Modal.Tabs = ModalTabs;
Modal.Body = ModalBody;
Modal.Footer = ModalFooter;
Modal.FooterMultiStep = ModalFooterMultiStep;
