/**
 * TEAM: frontend_infra
 * @flow
 */

import * as React from "react";
import classnames from "classnames";
import type {SheetEntry} from "aphrodite";
import StackingContext from "../StackingContext";

import {css, StyleSheet, _GLOBAL_STYLE_} from "../aphrodite-extended";
import {isTestEnvironment} from "../tools/test";
import {CUSTOM_MODAL_Z_INDEX} from "./Constants";

type ModalType = any;

// Modal has side effects that break server side rendering. Can only import it if we are in a valid environment.
// We set the Modal component equal to the default export from the Modal library.
let Modal: ?ModalType;

if (
  !isTestEnvironment() &&
  typeof window !== "undefined" &&
  window.document &&
  window.document.getElementsByTagName
) {
  // eslint-disable-next-line global-require
  Modal = require("react-modal");
  // should only perform this on the client. server side rendering doesn't have a document tag.
  // Make sure to bind modal to your appElement (https://reactcommunity.org/react-modal/accessibility/)
  Modal.setAppElement(document.getElementsByTagName("body")[0]);
}

type CloseOptions = {|
  onRequestClose: () => void,
  +shouldCloseOnEsc?: boolean,
  +shouldCloseOnOverlayClick?: boolean,
|};

export type ModalProps = {|
  /**
   * Not recommended to use this property.
   * If you decide to have this modal always rendered, use isOpen to control if it's open or closed.
   * Generally, it's better to have logic like: {isModalOpen ? <CustomModal /> : null}, because that
   * way there is a fresh new component in between modal toggles (otherwise form state would be maintined, etc.)
   * TODO(uforic): This has a default value of true, but HOCs don't handle default prop type resolution.
   */
  +isOpen?: boolean,
  /**
   * Close options are optional; if specified, they require at least an onRequestClose function.
   * Currently, we support "shouldCloseOnEsc" and "shouldCloseOnOverlayClick", which are disabled by default.
   */
  +closeOptions?: CloseOptions,
  /** Defines the style object of the black overlay */
  +overlayClassName?: string,
  /**
   * Defines the style object of the holding container that appears on the black overlay.
   * Required, but it is recommended that you start from the defaultModalStyles, defined and exported in
   * this file, which will set up the box shadow, border, etc. If you'd like to have a fixed width modal,
   * please choose from the sizes exported in defaultModalStyles, or add another size if it's likely to be reused.
   * Lastly, StaticGeneralModalLoader is implemented with customModal, so check it out to see how to use CustomModal.
   */
  +className: string,
  /** body of the modal */
  +children: React.Node,
|};

const defaultCloseOptions = {
  shouldCloseOnEsc: true,
  shouldCloseOnOverlayClick: false,
};

/**
 * @category Overlay
 * @short Full screen takeover, an semi-transparent black canvas drawn over the page.
 * @brandStatus V2
 * @status In Review
 * Avoid using this modal if you can; we have GeneralModalLoader, AlertModal, and ConfirmationModal for most use cases.
 * To use this modal, provide a css class that contains all the desired styles for the content pane. You'll likely
 * want to use the majority of the default styles for modals, which are exported from this file.
 * it will look like:
 *
 * ```
 * css(defaultModalStyles.content, your.custom.styles);
 * ```
 *
 * The most important style to specify yourself is maxWidth; without it, your modal will take up the whole screen.
 *
 * You may be tempted to use isOpen to toggle between modal visible states. Think twice about this - 95% of modal use cases
 * you _want_ the modal to unmount as it becomes visible / hidden. Otherwise, things like form state from the previous modal
 * opening may persist through. By default `isOpen` is true, and we recommend toggling it via {this.state.isModalShowing ? <CustomModal /> : null}
 *
 * **Testing**
 *
 * During unit tests, CustomModal will render into the DOM where you've placed it in the render function. This differs from its behavior on a browser,
 * where it renders into a react-portal at the top level (attached to the <body> of a page). This should make it easy to test toggling modal visibility.
 *
 * Technically, there is one last hurdle for visibility beyond rendering something that uses CustomModal. This is whether or not the global ModalStoreNew
 * is rendering the modal. If there is more than one, it will just render the last one in the list. In your test, you can call the method
 * isVisible (a method on the React class) in order to tell if your modal is the one that is being displayed.
 *
 * Uses [react-modal](https://reactcommunity.org/react-modal/).
 */
export default function CustomModal({
  children,
  className,
  overlayClassName,
  isOpen = true,
  ...props
}: ModalProps): React.Element<"div"> | React.Element<ModalType> {
  const {isDeprecatedZIndexEnabled, generalPortalElement} =
    React.useContext(StackingContext);

  if (Modal == null) {
    return <div className={className}>{children}</div>;
  }

  const closeOptions = props.closeOptions
    ? {...defaultCloseOptions, ...props.closeOptions}
    : undefined;

  return (
    <Modal
      overlayClassName={classnames(
        overlayClassName ||
          css(
            defaultModalStyles.overlay,
            isDeprecatedZIndexEnabled && defaultModalStyles.overlayZIndex
          ),
        css(defaultModalStyles[_GLOBAL_STYLE_])
      )}
      closeTimeoutMS={450}
      isOpen={isOpen}
      className={className}
      parentSelector={() => generalPortalElement}
      {...closeOptions}
    >
      {children}
    </Modal>
  );
}

// these styles are exported for users of CustomModal
// they have a lot of the defaults you probably want to use
// such as centering, margin from top, etc.
export const defaultModalStyles: {|
  content: SheetEntry,
  legacyContentStylesDoNotUse: SheetEntry,
  overlay: SheetEntry,
  widthLarge: SheetEntry,
  widthMedium: SheetEntry,
  widthSmall: SheetEntry,
  [typeof _GLOBAL_STYLE_]: SheetEntry,
|} = StyleSheet.create({
  overlay: {
    display: "block",
    backgroundColor: "rgba(0, 0, 0, 0.5)",
    position: "fixed",
    top: "0px",
    bottom: "0px",
    left: "0px",
    right: "0px",
    overflow: "auto",
  },
  overlayZIndex: {
    zIndex: CUSTOM_MODAL_Z_INDEX,
  },
  content: {
    boxShadow: "0 3px 9px rgba(0,0,0,0.5)",
    marginLeft: "auto",
    marginRight: "auto",
    marginTop: "80px",
    marginBottom: "80px",
    outline: "none",
    borderStyle: "none",
    borderRadius: "6px",
  },
  // ideally, custom modals have one of these widths
  widthSmall: {maxWidth: 360},
  widthMedium: {maxWidth: 500},
  widthLarge: {maxWidth: 760},
  // these styles are even more bare than the default content styles and are
  // only used for old components we are converting.
  legacyContentStylesDoNotUse: {
    marginLeft: "auto",
    marginRight: "auto",
    marginTop: "80px",
    outline: "none",
  },
  /** TODO(ctan): We add globals via Aphrodite to circumvent Next.js's
   * expectations, a better solution is to move off of react-modal due to style
   * conflicts and possible SSR issues. */
  [_GLOBAL_STYLE_]: {
    ".ReactModal__Content": {
      opacity: 0,
      transform: "translateY(50px)",
    },

    ".ReactModal__Content--after-open": {
      transition: "all 300ms ease-out",
      transitionDelay: "150ms",
      opacity: 1,
      transform: "translateY(0)",
    },

    ".ReactModal__Content--before-close": {
      transition: "all 300ms ease-out",
      opacity: 0,
      transform: "translateY(50px)",
    },

    ".ReactModal__Overlay": {
      opacity: 0,
    },

    ".ReactModal__Overlay--after-open": {
      transition: "all 200ms ease-out",
      opacity: 1,
    },

    ".ReactModal__Overlay--before-close": {
      transition: "all 200ms ease-out",
      transitionDelay: "150ms",
      opacity: 0,
    },
  },
});

export const getWidthStyle = (width: "s" | "m" | "l"): void | SheetEntry => {
  switch (width) {
    case "s":
      return defaultModalStyles.widthSmall;
    case "m":
      return defaultModalStyles.widthMedium;
    case "l":
      return defaultModalStyles.widthLarge;
    default:
      // eslint-disable-next-line no-unused-expressions
      (width: empty);
  }
};
