import { useEffect, useMemo, useRef, type CSSProperties } from "react";

type PopoverAnchor =
  | { kind: "point"; x: number; y: number }
  | { kind: "element"; element: HTMLElement };

type PopoverSide = "bottom" | "right";
type PopoverAlign = "start" | "end" | "center";

interface UseViewportPopoverOptions {
  anchor: PopoverAnchor;
  width: number;
  estimatedHeight: number;
  onClose: () => void;
  side?: PopoverSide;
  align?: PopoverAlign;
  offset?: number;
  viewportPadding?: number;
  ignoreElements?: Array<HTMLElement | null>;
}

export function useViewportPopover({
  anchor,
  width,
  estimatedHeight,
  onClose,
  side = "bottom",
  align = "start",
  offset = 8,
  viewportPadding = 16,
  ignoreElements = [],
}: UseViewportPopoverOptions) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    function handlePointerDown(event: MouseEvent) {
      const target = event.target as Node;
      if (ref.current?.contains(target)) {
        return;
      }
      if (ignoreElements.some((element) => element?.contains(target))) {
        return;
      }
      onClose();
    }

    function handleEscape(event: KeyboardEvent) {
      if (event.key === "Escape") {
        onClose();
      }
    }

    document.addEventListener("mousedown", handlePointerDown);
    window.addEventListener("keydown", handleEscape);

    return () => {
      document.removeEventListener("mousedown", handlePointerDown);
      window.removeEventListener("keydown", handleEscape);
    };
  }, [ignoreElements, onClose]);

  const style = useMemo<CSSProperties>(() => {
    let left = 0;
    let top = 0;

    if (anchor.kind === "element") {
      const rect = anchor.element.getBoundingClientRect();
      if (side === "right") {
        left = rect.right + offset;
        if (align === "end") {
          top = rect.bottom - estimatedHeight;
        } else if (align === "center") {
          top = rect.top + rect.height / 2 - estimatedHeight / 2;
        } else {
          top = rect.top;
        }
      } else {
        left = rect.left;
        if (align === "end") {
          left = rect.right - width;
        } else if (align === "center") {
          left = rect.left + rect.width / 2 - width / 2;
        }
        top = rect.bottom + offset;
      }
    } else {
      left = anchor.x;
      top = anchor.y + offset;

      if (align === "end") {
        left = anchor.x - width;
      } else if (align === "center") {
        left = anchor.x - width / 2;
      }
    }

    const maxLeft = Math.max(viewportPadding, window.innerWidth - width - viewportPadding);
    const maxTop = Math.max(viewportPadding, window.innerHeight - estimatedHeight - viewportPadding);

    return {
      position: "fixed",
      left: Math.min(Math.max(left, viewportPadding), maxLeft),
      top: Math.min(Math.max(top, viewportPadding), maxTop),
      width,
      zIndex: 60,
    };
  }, [align, anchor, estimatedHeight, offset, side, viewportPadding, width]);

  return { ref, style };
}
