import { useCallback, useEffect, useRef, useState, type RefObject } from "react";

type HorizontalAlign = "start" | "end" | "center";
type VerticalAlign = "start" | "end" | "center";
type OverlaySide = "bottom" | "right";

interface UseAnchoredOverlayOptions<TTrigger extends HTMLElement> {
  open: boolean;
  onClose: () => void;
  offset?: number;
  viewportPadding?: number;
  side?: OverlaySide;
  align?: HorizontalAlign;
  crossAlign?: VerticalAlign;
  matchTriggerWidth?: boolean;
  triggerRef?: RefObject<TTrigger | null>;
  ignoreElements?: Array<HTMLElement | null>;
  ignoreSelectors?: string[];
}

interface OverlayPosition {
  top: number;
  left: number;
  minWidth?: number;
}

export function useAnchoredOverlay<TTrigger extends HTMLElement = HTMLElement>({
  open,
  onClose,
  offset = 8,
  viewportPadding = 16,
  side = "bottom",
  align = "start",
  crossAlign = "start",
  matchTriggerWidth = false,
  triggerRef: externalTriggerRef,
  ignoreElements = [],
  ignoreSelectors = [],
}: UseAnchoredOverlayOptions<TTrigger>) {
  const internalTriggerRef = useRef<TTrigger | null>(null);
  const triggerRef = externalTriggerRef ?? internalTriggerRef;
  const panelRef = useRef<HTMLDivElement | null>(null);
  const frameRef = useRef<number | null>(null);
  const [position, setPosition] = useState<OverlayPosition>({ top: 0, left: 0 });

  const updatePosition = useCallback(() => {
    const trigger = triggerRef.current;
    if (!trigger || !trigger.isConnected) {
      onClose();
      return;
    }

    const rect = trigger.getBoundingClientRect();
    const panelWidth = panelRef.current?.offsetWidth ?? rect.width;
    const panelHeight = panelRef.current?.offsetHeight ?? 0;

    let nextTop = rect.bottom + offset;
    let nextLeft = rect.left;

    if (side === "right") {
      nextLeft = rect.right + offset;
      if (crossAlign === "center") {
        nextTop = rect.top + rect.height / 2 - panelHeight / 2;
      } else if (crossAlign === "end") {
        nextTop = rect.bottom - panelHeight;
      } else {
        nextTop = rect.top;
      }
    } else {
      if (align === "end") {
        nextLeft = rect.right - panelWidth;
      } else if (align === "center") {
        nextLeft = rect.left + rect.width / 2 - panelWidth / 2;
      }

      nextTop = rect.bottom + offset;
      const nextBottom = nextTop + panelHeight;
      const flippedTop = rect.top - panelHeight - offset;
      if (panelHeight > 0 && nextBottom > window.innerHeight - viewportPadding && flippedTop >= viewportPadding) {
        nextTop = flippedTop;
      }
    }

    const boundedLeft = Math.min(
      Math.max(nextLeft, viewportPadding),
      Math.max(viewportPadding, window.innerWidth - panelWidth - viewportPadding),
    );
    const boundedTop = Math.min(
      Math.max(nextTop, viewportPadding),
      Math.max(viewportPadding, window.innerHeight - panelHeight - viewportPadding),
    );

    setPosition({
      top: boundedTop,
      left: boundedLeft,
      ...(matchTriggerWidth ? { minWidth: rect.width } : {}),
    });
  }, [align, crossAlign, matchTriggerWidth, offset, onClose, side, triggerRef, viewportPadding]);

  useEffect(() => {
    if (!open) {
      return;
    }

    function handlePointerDown(event: PointerEvent) {
      const target = event.target;
      if (!(target instanceof Node)) {
        return;
      }
      if (triggerRef.current?.contains(target) || panelRef.current?.contains(target)) {
        return;
      }
      if (ignoreElements.some((element) => element?.contains(target))) {
        return;
      }
      if (
        target instanceof Element
        && ignoreSelectors.some((selector) => target.closest(selector) !== null)
      ) {
        return;
      }
      onClose();
    }

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

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

    return () => {
      document.removeEventListener("pointerdown", handlePointerDown, true);
      window.removeEventListener("keydown", handleEscape);
    };
  }, [ignoreElements, ignoreSelectors, onClose, open, triggerRef]);

  useEffect(() => {
    if (!open) {
      return;
    }

    function cancelScheduledFrame() {
      if (frameRef.current === null) {
        return;
      }
      cancelAnimationFrame(frameRef.current);
      frameRef.current = null;
    }

    function scheduleUpdate() {
      if (frameRef.current !== null) {
        return;
      }
      frameRef.current = requestAnimationFrame(() => {
        frameRef.current = null;
        updatePosition();
      });
    }

    updatePosition();
    scheduleUpdate();

    window.addEventListener("resize", scheduleUpdate, { passive: true });
    window.addEventListener("scroll", scheduleUpdate, true);
    window.visualViewport?.addEventListener("resize", scheduleUpdate, { passive: true });
    window.visualViewport?.addEventListener("scroll", scheduleUpdate, { passive: true });

    return () => {
      cancelScheduledFrame();
      window.removeEventListener("resize", scheduleUpdate);
      window.removeEventListener("scroll", scheduleUpdate, true);
      window.visualViewport?.removeEventListener("resize", scheduleUpdate);
      window.visualViewport?.removeEventListener("scroll", scheduleUpdate);
    };
  }, [open, updatePosition]);

  const handleOpenChange = useCallback((nextOpen: boolean) => {
    if (nextOpen) {
      updatePosition();
      return;
    }
    onClose();
  }, [onClose, updatePosition]);

  return {
    triggerRef,
    panelRef,
    position,
    updatePosition,
    handleOpenChange,
  };
}
