import {
  type VariantProps,
  getCalculatedTransitionDuration,
  useDocument,
  vcn,
} from "@pswui-lib";
import React, { type MutableRefObject, useEffect, useId, useRef } from "react";
import ReactDOM from "react-dom";

import {
  type ToastOption,
  close,
  defaultToastOption,
  getSingleSnapshot,
  getSnapshot,
  notify,
  notifySingle,
  subscribe,
  subscribeSingle,
  toasts,
} from "./Store";
import { toastVariant } from "./Variant";

const ToastTemplate = ({
  id,
  globalOption,
}: {
  id: `${number}`;
  globalOption: ToastOption;
}) => {
  const [toast, setToast] = React.useState<(typeof toasts)[`${number}`]>(
    toasts[id],
  );
  const ref = React.useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    subscribeSingle(id)(() => {
      setToast(getSingleSnapshot(id)());
    });
  }, [id]);

  const toastData = {
    ...globalOption,
    ...toast,
  };

  React.useEffect(() => {
    if (toastData.life === "born") {
      requestAnimationFrame(function untilBorn() {
        /*
        To confirm that the toast is rendered as "born" state and then change to "normal" state
        This way will make sure born -> normal stage transition animation will work.
        */
        const elm = document.querySelector(
          `div[data-toaster-root] > div[data-toast-id="${id}"][data-toast-lifecycle="born"]`,
        );
        if (!elm) return requestAnimationFrame(untilBorn);

        toasts[id] = {
          ...toasts[id],
          life: "normal",
        };
        notifySingle(id);
      });
    }
    if (toastData.life === "normal" && toastData.closeTimeout !== null) {
      const timeout = setTimeout(() => {
        close(id);
      }, toastData.closeTimeout);
      return () => clearTimeout(timeout);
    }
    if (toastData.life === "dead") {
      let calculatedTransitionDurationMs = 1;
      if (ref.current)
        calculatedTransitionDurationMs = getCalculatedTransitionDuration(
          ref as MutableRefObject<HTMLDivElement>,
        );
      const timeout = setTimeout(() => {
        delete toasts[id];
        notify();
      }, calculatedTransitionDurationMs);
      return () => clearTimeout(timeout);
    }
  }, [id, toastData.life, toastData.closeTimeout]);

  return (
    <div
      className={toastVariant({
        status: toastData.status,
        life: toastData.life,
      })}
      ref={ref}
      data-toast-id={id}
      data-toast-lifecycle={toastData.life}
    >
      {toastData.closeButton && (
        <button
          className="absolute top-2 right-2"
          onClick={() => close(id)}
          type={"button"}
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="1.2rem"
            height="1.2rem"
            viewBox="0 0 24 24"
          >
            <title>Close</title>
            <path
              fill="currentColor"
              d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"
            />
          </svg>
        </button>
      )}
      <div className="text-sm font-bold">{toastData.title}</div>
      <div className="text-sm">{toastData.description}</div>
    </div>
  );
};

const [toasterVariant, resolveToasterVariantProps] = vcn({
  base: "fixed p-4 flex flex-col gap-4 top-0 right-0 w-full md:max-w-md md:bottom-0 md:top-auto pointer-events-none z-40",
  variants: {},
  defaults: {},
});

interface ToasterProps
  extends React.ComponentPropsWithoutRef<"div">,
    VariantProps<typeof toasterVariant> {
  defaultOption?: Partial<ToastOption>;
  muteDuplicationWarning?: boolean;
}

const Toaster = React.forwardRef<HTMLDivElement, ToasterProps>((props, ref) => {
  const id = useId();
  const [variantProps, otherPropsCompressed] =
    resolveToasterVariantProps(props);
  const { defaultOption, muteDuplicationWarning, ...otherPropsExtracted } =
    otherPropsCompressed;

  const [toastList, setToastList] = React.useState<typeof toasts>(toasts);
  const internalRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    return subscribe(() => {
      setToastList(getSnapshot());
    });
  }, []);

  const option = React.useMemo(() => {
    return {
      ...defaultToastOption,
      ...defaultOption,
    };
  }, [defaultOption]);

  const document = useDocument();

  if (!document) return null;

  const toasterInstance = document.querySelector("div[data-toaster-root]");
  if (toasterInstance && id !== toasterInstance.id) {
    if (process.env.NODE_ENV === "development" && !muteDuplicationWarning) {
      console.warn(
        "Multiple Toaster instances detected. Only one Toaster is allowed.",
      );
    }
    return null;
  }

  return (
    <>
      {ReactDOM.createPortal(
        <div
          {...otherPropsExtracted}
          data-toaster-root={true}
          className={toasterVariant(variantProps)}
          ref={(el) => {
            internalRef.current = el;
            if (typeof ref === "function") {
              ref(el);
            } else if (ref) {
              ref.current = el;
            }
          }}
          id={id}
        >
          {Object.entries(toastList).map(([id]) => (
            <ToastTemplate
              key={id}
              id={id as `${number}`}
              globalOption={option}
            />
          ))}
        </div>,
        document.body,
      )}
    </>
  );
});
Toaster.displayName = "Toaster";

export { Toaster };