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

import * as React from "react";
import {css, StyleSheet} from "aphrodite";
import {TransitionGroup, CSSTransition} from "react-transition-group";
// TODO: Fix this eslint issue on next edit. This is an autogenerated comment.
// eslint-disable-next-line flexport/no-legacy-dependencies
import {zIndices} from "latitude/tools/deprecatedZIndices";
import Portal from "latitude/Portal";
import {padding} from "../styles/deprecatedWhitespace";
import Toast, {type ToastProps} from "./Toast";
import ToasterContext from "./ToasterContext";
import StackingContext from "../StackingContext";

const DEFAULT_TOAST_DURATION = 3000;

export type ToasterProps = {
  /* Toaster provides context for displaying toasts to its children, a Toaster must have children */
  +children: React.Node,
  /* Option to add space to the top so it doesn't overlap with a header */
  +topOffset?: number,
  ...
};

type ToastEntry = {|+id: number, +props: ToastProps, +timeoutId: TimeoutID|};

/**
 * Toaster is an invisible full page overlay which manages and displays incoming and outgoing toasts. Head over to [Toast Documention](/design/components/notifications/toast#Toast) for examples, UI anatomy, and toast options.
 *
 * **Adding toasts:**
 *
 * 1. Ensure Toaster is rendered in your app (`import Toaster from "latitude/toast/Toaster";`)
 *
 * 2. Elsewhere in your app, use useToaster (`import useToaster from "latitude/toast/useToaster";`) or ToasterContext (`import ToasterContext from "latitude/toast/ToasterContext";`) to push a toast: `const {showToast} = useToaster(); showToast({message, intent});` or `<ToasterContext.Consumer>{({showToast}) => ...}</ToasterContext.Consumer>
 *
 * **How it works**
 *
 * Toasts will automatically stack and disappear according to the default settings.
 *
 * @short The manager that controls Toast animation and display
 * @brandStatus V2
 * @status Stable
 * @category Feedback */
const Toaster: React.AbstractComponent<ToasterProps, mixed> =
  React.memo<ToasterProps>(function Toaster({
    topOffset,
    children,
  }: ToasterProps) {
    const [toasts, setToasts] = React.useState<$ReadOnlyArray<ToastEntry>>([]);
    const toastsIdCounter = React.useRef(0);
    // Context value should never change to avoid re-renders
    const contextValue = React.useRef({
      showToast: (
        toastProps: ToastProps,
        removeAfter: number = DEFAULT_TOAST_DURATION
      ) => {
        const id = toastsIdCounter.current;
        const timeoutId = setTimeout(
          contextValue.current._removeToast,
          removeAfter,
          id
        );

        toastsIdCounter.current += 1;

        setToasts(prevToasts => [
          ...prevToasts,
          {id, props: toastProps, timeoutId},
        ]);

        return id;
      },
      _removeToast: (toastIdToRemove: number) => {
        setToasts(prevToasts => [
          ...prevToasts.filter(toast => toast.id !== toastIdToRemove),
        ]);
      },
    });
    const {isDeprecatedZIndexEnabled} = React.useContext(StackingContext);

    React.useEffect(
      () =>
        function cleanup() {
          toasts.map(toast => clearTimeout(toast.timeoutId));
        },
      // we only want to clearTimeout on unmount
      // eslint-disable-next-line react-hooks/exhaustive-deps
      []
    );

    return (
      <ToasterContext.Provider value={contextValue.current}>
        <>
          {children}
          <Portal type="systemOverlay">
            <TransitionGroup
              className={css(
                styles.toaster,
                padding.t.m,
                isDeprecatedZIndexEnabled
                  ? styles.toasterZIndex
                  : styles.toasterZIndexDisabled
              )}
              style={{top: topOffset || 0}}
            >
              {toasts.map(toast => (
                <CSSTransition
                  key={`${toast.props.message}-${toast.id}`}
                  timeout={300}
                  classNames={{
                    enter: css(styles.toastEnter),
                    enterActive: css(styles.toastEnterActive),
                    exit: css(styles.toastExit),
                    exitActive: css(styles.toastExitActive),
                  }}
                >
                  {/* The active-toast class is used by the Flexport gmail extension,
                   * please look there before removing this - benbernard 2018-08-09
                   */}
                  {/* eslint-disable-next-line flexport/no-oocss */}
                  <div className="active-toast">
                    <Toast {...toast.props} />
                  </div>
                </CSSTransition>
              ))}
            </TransitionGroup>
          </Portal>
        </>
      </ToasterContext.Provider>
    );
  });

Toaster.displayName = "Toaster";

export default Toaster;

const styles = StyleSheet.create({
  toaster: {
    position: "fixed",
    left: "50%",
    transform: "translateX(-50%)",
    textAlign: "center",
  },
  toasterZIndex: {
    zIndex: zIndices.zIndex1500AboveModal.zIndex,
  },
  toasterZIndexDisabled: {
    zIndex: 2,
  },
  toastEnter: {
    opacity: 0,
    transform: "translateX(-64px)",
  },
  toastEnterActive: {
    opacity: 1,
    transform: "translateX(0)",
    transition: "all 0.3s cubic-bezier(.42,0,.58,1)",
  },
  toastExit: {
    opacity: 1,
    transform: "translateX(0)",
  },
  toastExitActive: {
    opacity: 0,
    transform: "translateX(48px)",
    transition: "all 0.3s cubic-bezier(.42,0,.58,1)",
  },
});
