import { Icon, IconifyIconProps } from '@iconify/react/dist/iconify.js';
import cx from 'clsx';
import { filter, isEqual } from 'lodash-es';
import {
  createContext,
  createRef,
  ReactNode,
  RefObject,
  useCallback,
  useContext,
  useEffect,
  useState,
} from 'react';
import { createPortal } from 'react-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { v4 } from 'uuid';
import styles from './Notification.module.css';

export enum NotificationType {
  INFO,
  PROMPTION,
  SUCCESS,
  WARNING,
  ERROR,
  CUSTOM,
}

export enum ToastDuration {
  MANUAL = 0,
  SHORT = 1500,
  MEDIUM = 3000,
  LONG = 5000,
}

interface NotificationFunctions {
  addNotification(
    kind: NotificationType,
    title?: string,
    message?: ReactNode,
    icon?: IconifyIconProps['icon'],
    duration?: number,
  ): string;
  removeNotification(id: string): void;
  showToast(
    kind: NotificationType,
    message: string,
    icon?: IconifyIconProps['icon'],
    duration?: ToastDuration,
  ): string;
}

const NotificationStyleMap = {
  [NotificationType.INFO]: styles.kind_info,
  [NotificationType.PROMPTION]: styles.kind_promption,
  [NotificationType.SUCCESS]: styles.kind_success,
  [NotificationType.WARNING]: styles.kind_warning,
  [NotificationType.ERROR]: styles.kind_error,
  [NotificationType.CUSTOM]: styles.kind_custom,
};

const NotificationHostContext = createContext<NotificationFunctions>({
  addNotification: () => '',
  removeNotification: () => {},
  showToast: () => '',
});

type NotificationProps = {
  kind?: NotificationType;
  nid: string;
  icon?: IconifyIconProps['icon'];
  title?: string;
  message?: ReactNode;
  duration?: number;
  ref: RefObject<HTMLDivElement>;
  closeAction?: (id: string) => void;
};
const Notification = ({
  kind = NotificationType.INFO,
  nid,
  icon,
  title,
  message,
  duration = 3000,
  ref,
  closeAction,
}: NotificationProps) => {
  useEffect(() => {
    if (duration > 0) {
      const timer = setTimeout(() => {
        closeAction?.(nid);
      }, duration);
      return () => clearTimeout(timer);
    }
  }, [nid, duration, closeAction]);

  return (
    <div className={cx(styles.notification, NotificationStyleMap[kind])} ref={ref}>
      {icon && <Icon icon={icon} className={cx(styles.notification_icon)} />}
      <div className={cx(styles.notificaiton_content)}>
        {title && <div className={cx(styles.notification_title)}>{title}</div>}
        {message && <div>{message}</div>}
      </div>
      {duration === 0 && (
        <Icon
          icon="tabler:x"
          className={styles.notification_close}
          onClick={() => closeAction?.(nid)}
        />
      )}
    </div>
  );
};

type ToastProps = {
  kind: NotificationType;
  tid: string;
  message?: string;
  icon?: string;
  duration?: ToastDuration;
  ref: RefObject<HTMLDivElement>;
  closeAction: () => void;
};
const Toast = ({
  kind,
  tid,
  message,
  icon,
  duration = ToastDuration.MEDIUM,
  ref,
  closeAction,
}: ToastProps) => {
  useEffect(() => {
    if (duration !== ToastDuration.MANUAL) {
      const timer = setTimeout(() => {
        closeAction?.(tid);
      }, duration);
      return () => clearTimeout(timer);
    }
  }, [tid, duration, closeAction]);

  return (
    <div className={cx(styles.toast, NotificationStyleMap[kind])} ref={ref}>
      {icon && <Icon icon={icon} className={styles.toast_icon} />}
      <div className={styles.toast_content}>{message}</div>
      {duration === ToastDuration.MANUAL && (
        <Icon icon="tabler:x" className={styles.toast_close} onClick={() => closeAction?.(tid)} />
      )}
    </div>
  );
};

export function useNotification() {
  const functions = useContext<NotificationFunctions>(NotificationHostContext);
  return functions;
}

type NotificationElement = {
  id: string;
  element: ReactNode;
  ref: RefObject<ReactNode>;
};
type NotificationsProps = {
  defaultDuration?: number;
  maxNotifications?: number;
  children?: ReactNode;
};
export function Notifications({
  defaultDuration = 3000,
  maxNotifications = 5,
  children,
}: NotificationsProps) {
  const [notifications, setNotifications] = useState<NotificationElement[]>([]);
  const [toasts, setToasts] = useState<NotificationElement[]>([]);

  const removeNotification = useCallback((id: string) => {
    setNotifications((prev) => filter(prev, (n) => !isEqual(n.id, id)));
  }, []);
  const addNotification = useCallback(
    (
      kind: NotificationType,
      title?: string,
      message?: ReactNode,
      icon?: IconifyIconProps['icon'],
      duration?: number,
    ) => {
      const id = v4();
      const ref = createRef(null);
      const newNotify = (
        <Notification
          kind={kind}
          nid={id}
          icon={icon}
          title={title}
          message={message}
          duration={duration ?? defaultDuration}
          closeAction={removeNotification}
          ref={ref}
        />
      );
      setNotifications((prev) => [...prev, { id, element: newNotify, ref } as NotificationElement]);

      return id;
    },
    [removeNotification, defaultDuration],
  );
  const removeToast = useCallback((id: string) => {
    setToasts((prev) => filter(prev, (n) => !isEqual(n.id, id)));
  }, []);
  const showToast = useCallback(
    (
      kind: NotificationType,
      message?: string,
      icon?: IconifyIconProps['icon'],
      duration?: ToastDuration,
    ) => {
      const id = v4();
      const ref = createRef(null);
      const newToast = (
        <Toast
          kind={kind}
          tid={id}
          message={message ?? ''}
          icon={icon}
          duration={duration ?? ToastDuration.MEDIUM}
          ref={ref}
          closeAction={removeToast}
        />
      );
      setToasts((prev) => [...prev, { id, element: newToast, ref } as NotificationElement]);

      return id;
    },
    [removeToast],
  );

  return (
    <NotificationHostContext
      value={{
        addNotification,
        removeNotification,
        showDialog: () => '',
        showModalDialog: () => '',
        closeDialog: () => {},
        showToast,
      }}>
      {children}
      {createPortal(
        <div className={cx(styles.notification_positioner)}>
          <TransitionGroup component={null}>
            {notifications.slice(0, maxNotifications).map(({ id, element, ref }) => (
              <CSSTransition
                key={id}
                nodeRef={ref}
                unmountOnExit
                timeout={500}
                classNames={{
                  enter: styles.slide_in_enter,
                  enterActive: styles.slide_in_enter_active,
                  exitActive: styles.slide_out_exit_active,
                }}>
                {element}
              </CSSTransition>
            ))}
          </TransitionGroup>
        </div>,
        document.body,
      )}
      {createPortal(
        <div className={cx(styles.toast_positioner)}>
          <TransitionGroup component={null}>
            {toasts.slice(0, 1).map(({ id, element, ref }) => (
              <CSSTransition
                key={id}
                nodeRef={ref}
                unmountOnExit
                timeout={500}
                classNames={{
                  enter: styles.fade_in_enter,
                  enterActive: styles.fade_in_enter_active,
                  exitActive: styles.fade_out_exit_active,
                }}>
                {element}
              </CSSTransition>
            ))}
          </TransitionGroup>
        </div>,
        document.body,
      )}
    </NotificationHostContext>
  );
}
