import {
  Dispatch,
  FC,
  PropsWithChildren,
  SetStateAction,
  useCallback,
  useEffect,
  useState,
} from "react";
import { bulkRound } from "../../utils/bulkRound";
import { clamp } from "../../utils/clamp";
import { Portal } from "../../portal/components";

type edge = "top" | "bottom" | "left" | "right";
type alignment = edge | "centered";

interface IProps {
  preferredEdge: edge;
  preferredAlign: alignment;
  relativeElement: HTMLElement;
  spacing?: number;
  isClosing: boolean;
  isClosed: boolean;
  setHover: Dispatch<SetStateAction<boolean>>;
}

type position = { top: number; left: number; width?: number };

export const PoppableContent: FC<PropsWithChildren<IProps>> = (
  {
    preferredAlign,
    preferredEdge,
    children,
    relativeElement,
    spacing = 10,
    setHover,
    isClosing,
    isClosed,
  },
) => {
  const [popRef, setPopRef] = useState<HTMLDivElement>();
  const updateRef = useCallback((node: HTMLDivElement) => {
    if (!node) return;
    setPopRef(node);
  }, []);

  const getAlignment = useCallback(
    (
      relX: number,
      relY: number,
      relWidth: number,
      relHeight: number,
      popWidth: number,
      popHeight: number,
      edge: edge,
      align: alignment,
    ): position => {
      const pos = {
        top: relY,
        left: relX,
      };

      switch (align) {
        case "centered":
          pos.top = relY + (relHeight / 2) - (popHeight / 2);
          pos.left = relX + (relWidth / 2) - (popWidth / 2);
          break;
        case "top":
          pos.top = relY;
          break;
        case "bottom":
          pos.top = relY + relHeight - popHeight;
          break;
        case "left":
          pos.left = relX;
          break;
        case "right":
          pos.left = relX + relWidth - popWidth;
          break;
      }

      return pos;
    },
    [],
  );

  const getPosition = useCallback(
    (popWidth: number, popHeight: number, edge: edge, align: alignment) => {
      const rel = relativeElement.getBoundingClientRect();
      const [relX, relY, relWidth, relHeight] = bulkRound(
        rel.x,
        rel.y,
        rel.width,
        rel.height,
      );

      const pos: position = { top: 100, left: 100 };
      const alignment = getAlignment(
        relX,
        relY,
        relWidth,
        relHeight,
        popWidth,
        popHeight,
        edge,
        align,
      );

      switch (edge) {
        case "top":
          pos.top = relY - popHeight - spacing +
            document.documentElement.scrollTop;
          pos.left = alignment.left;
          break;
        case "bottom":
          pos.top = relY + relHeight + spacing +
            document.documentElement.scrollTop;
          pos.left = alignment.left;
          break;
        case "left":
          pos.left = relX - popWidth - spacing;
          pos.top = alignment.top + document.documentElement.scrollTop;
          break;
        case "right":
          pos.left = relX + relWidth + spacing;
          pos.top = alignment.top + document.documentElement.scrollTop;
          break;
      }

      return pos;
    },
    [getAlignment, relativeElement, spacing],
  );

  const getClampedPosition = useCallback(() => {
    if (!popRef) return { opacity: 0 };

    const pop = popRef.getBoundingClientRect();
    const [popWidth, popHeight] = bulkRound(pop.width, pop.height);

    const pos = getPosition(popWidth, popHeight, preferredEdge, preferredAlign);
    const { innerHeight, innerWidth } = window;

    pos.top = ["left", "right"].includes(preferredEdge)
      ? clamp(pos.top, spacing, innerHeight - popHeight - spacing)
      : pos.top;
    pos.left = ["top", "bottom"].includes(preferredEdge)
      ? clamp(pos.left, spacing, innerWidth - popWidth - spacing)
      : pos.left;

    return pos;
  }, [popRef, getPosition, preferredEdge, preferredAlign, spacing]);

  return (
    <Portal>
      <div
        ref={updateRef}
        style={getClampedPosition()}
        data-fading={isClosing}
        data-visible={!isClosing}
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => setHover(false)}
        // className="absolute  w-[400px] border"
        className="poppable"
      >
        {children}
      </div>
    </Portal>
  );
};
