import React, { useEffect, useState } from "react";
import toaster from "toasted-notes";
import classNames from "classnames";
import { Action } from "../../types";
import { bemHOF } from "../../utilities/bem";
import { Box } from "../Box";
import { CloseButton } from "../CloseButton";
import { Icon, IconProps, ICON_TYPE } from "../Icon";
import { Link } from "../Link";
import { Text } from "../Text";

const cn = bemHOF("Snackbar");

const POSITION = "bottom-left";

interface SharedProps {
  /**
   * The message shown in the Snackbar; ideally short and succinct
   */
  message: string;
  /**
   * Adds an action next to the message
   */
  action?: Action;
  /**
   * When a loading icon is detected, it will automatically spin.
   *
   * Sizing is handled automatically, too.
   */
  icon?: ICON_TYPE;
  /**
   * The amount of time (in milliseconds) the snackbar appears on-screen. If
   * undefined, then the Snackbar will stay on-screen indefinitely. An undefined
   * duration would be used in cases when the Snackbar is used as a loading
   * message (e.g., an in progress API call).
   */
  duration?: number | null;
  /**
   * Prevent the close button from rendering. Useful in cases when the Snackbar
   * is used as a loading message (e.g., an in progress API call).
   */
  disableClose?: boolean;
}

interface ShowSnackbarProps extends SharedProps {
  /**
   * Classname to apply to the Snackbar root element
   */
  className?: string;
}

interface SnackbarProps extends ShowSnackbarProps {
  onClose: () => void;
  id?: string;
}

interface SnackbarHookProps extends SharedProps {
  /**
   * Whether or not the Snackar is visible
   */
  isVisible: boolean;
}

const hideSnackbar = (id: number) => toaster.close(id, POSITION);

const SnackbarAction = ({ label, onClick, href, external }: Action) => {
  const Component = typeof onClick === "function" ? "button" : "a";
  const type = Component === "button" ? "button" : undefined;

  return (
    <Link
      as={Component}
      type={type}
      className={cn({ e: "action" })}
      onClick={onClick}
      href={href}
      target={external ? "_blank" : undefined}
    >
      {label}
    </Link>
  );
};

export const Snackbar = ({
  message,
  action,
  icon,
  id,
  className,
  onClose,
  disableClose,
}: SnackbarProps) => {
  const spin = icon === ICON_TYPE.SPINNER || icon === ICON_TYPE.CIRCLE_NOTCH;
  const iconProps: IconProps | undefined = icon
    ? {
        icon,
        spin,
      }
    : undefined;

  return (
    <Box className={classNames(cn(), className)} id={id}>
      {iconProps && <Icon {...iconProps} className={cn({ e: "icon" })} />}
      <Text>{message}</Text>
      {action && action.label && (
        <SnackbarAction
          {...action}
          onClick={() => {
            if (action.onClick) {
              action.onClick();
            }
            onClose();
          }}
        />
      )}
      {!disableClose && (
        <CloseButton
          className={classNames(cn({ e: "dismiss" }))}
          onClick={onClose}
        />
      )}
    </Box>
  );
};

export const showSnackbar = ({
  message,
  action,
  icon,
  duration = null,
  className,
  disableClose,
}: ShowSnackbarProps) =>
  toaster.notify(
    ({ onClose, id }: { onClose: () => void; id: number }) => {
      return (
        <Snackbar
          {...{
            message,
            onClose,
            action,
            icon,
            className,
            disableClose,
            id: `snackbar-component-${id}`,
          }}
        />
      );
    },
    {
      position: POSITION,
      duration,
    },
  );

export const useSnackbar = ({
  message,
  action,
  isVisible,
  icon,
  duration,
  disableClose,
}: SnackbarHookProps) => {
  const [snackbar, setSnackbar] = useState<{ id: number; position: string }>({
    id: 0,
    position: POSITION,
  });
  const [wasViewed, setWasViewed] = useState<boolean>(false);

  useEffect(() => {
    if (isVisible && !wasViewed) {
      setSnackbar(
        showSnackbar({
          message,
          action,
          icon,
          duration,
          disableClose,
        }),
      );
      setWasViewed(true);
    }

    if (!isVisible && wasViewed) {
      hideSnackbar(snackbar.id);
      setWasViewed(false);
    }
  }, [
    message,
    action,
    isVisible,
    icon,
    duration,
    disableClose,
    snackbar,
    wasViewed,
  ]);
};
