import { createContext, useCallback, useContext, useRef, useState } from "react";

import { StyledProvider, StyledViewport } from "./toast.styled";
import { Toast } from "./toast";

import type * as Stitches from "@stitches/react";

type ToastProps = React.ComponentProps<typeof Toast> & { id: number };

interface ToastContext {
  toasts: Array<ToastProps>;
  publish: (newToast: Omit<ToastProps, "id">) => () => void;
  removeToastById: (id: ToastProps["id"]) => void;
}

const ToastContext = createContext<ToastContext>({
  toasts: [],
  publish: () => () => null,
  removeToastById: () => null,
});

type ViewportVariants = Stitches.VariantProps<typeof StyledViewport>;

interface Props extends React.ComponentProps<typeof StyledProvider> {
  /**
   * The position of the toast.
   */
  position?: ViewportVariants["position"];
  /**
   * Props to be passed to the __StyledViewport__ component.
   */
  viewportProps?: React.ComponentProps<typeof StyledViewport>;
}

export const ToastProvider: React.FC<Props> = ({ children, viewportProps, position, ...rest }) => {
  const [toasts, setToasts] = useState<ToastContext["toasts"]>([]);
  const toastId = useRef(0);

  const removeToastById = useCallback((idToRemove: ToastProps["id"]) => {
    setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== idToRemove));
  }, []);

  const publish = useCallback(
    (newToast: Omit<ToastProps, "id">) => {
      const newToastId = toastId.current++;
      setToasts((prevToasts) => {
        return [...prevToasts, { ...newToast, id: newToastId }];
      });

      return () => removeToastById(newToastId);
    },
    [removeToastById]
  );

  return (
    <ToastContext.Provider value={{ toasts, publish, removeToastById }}>
      <StyledProvider {...rest}>
        <StyledViewport {...viewportProps} position={position} />
        {children}
        {toasts.map(({ id, ...toastProps }) => {
          return <Toast key={id} id={id} {...toastProps} />;
        })}
      </StyledProvider>
    </ToastContext.Provider>
  );
};

export const useToast = () => {
  return useContext(ToastContext);
};
