import { useCallback, useEffect, useState, type CSSProperties, type ReactNode } from "react";
import { createPortal } from "react-dom";
import { cn } from "../lib/cn";

type NodeDcDropdownPlacement = "bottom-start" | "bottom-end" | "top-start" | "top-end";

interface NodeDcDropdownTriggerApi {
  open: boolean;
  toggle: () => void;
  close: () => void;
  setTriggerRef: (node: HTMLElement | null) => void;
}

interface NodeDcDropdownProps {
  trigger: (api: NodeDcDropdownTriggerApi) => ReactNode;
  children: ReactNode | ((api: { close: () => void }) => ReactNode);
  placement?: NodeDcDropdownPlacement;
  minWidth?: number;
  width?: number;
  offset?: number;
  className?: string;
  surfaceClassName?: string;
  disabled?: boolean;
}

export function NodeDcDropdown({
  trigger,
  children,
  placement = "bottom-start",
  minWidth = 180,
  width,
  offset = 8,
  className,
  surfaceClassName,
  disabled = false,
}: NodeDcDropdownProps) {
  const [open, setOpen] = useState(false);
  const [triggerEl, setTriggerEl] = useState<HTMLElement | null>(null);
  const [style, setStyle] = useState<CSSProperties>();

  const close = useCallback(() => setOpen(false), []);

  const updatePosition = useCallback(() => {
    if (!triggerEl) return;

    const rect = triggerEl.getBoundingClientRect();
    const surfaceWidth = width ?? Math.max(rect.width, minWidth);
    const viewportPadding = 8;
    const alignedLeft = placement.endsWith("end") ? rect.right - surfaceWidth : rect.left;
    const left = Math.min(Math.max(viewportPadding, alignedLeft), window.innerWidth - surfaceWidth - viewportPadding);

    if (placement.startsWith("top")) {
      setStyle({
        bottom: window.innerHeight - rect.top + offset,
        left,
        width: surfaceWidth,
      });
      return;
    }

    setStyle({
      top: rect.bottom + offset,
      left,
      width: surfaceWidth,
    });
  }, [minWidth, offset, placement, triggerEl, width]);

  const toggle = useCallback(() => {
    if (disabled) return;
    setOpen((current) => !current);
  }, [disabled]);

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

    updatePosition();

    const handlePointerDown = (event: PointerEvent) => {
      const target = event.target as HTMLElement | null;

      if (target && (triggerEl?.contains(target) || target.closest("[data-nodedc-dropdown-surface='true']"))) {
        return;
      }

      close();
    };

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") close();
    };

    window.addEventListener("resize", updatePosition);
    window.addEventListener("scroll", updatePosition, true);
    document.addEventListener("pointerdown", handlePointerDown);
    document.addEventListener("keydown", handleKeyDown);

    return () => {
      window.removeEventListener("resize", updatePosition);
      window.removeEventListener("scroll", updatePosition, true);
      document.removeEventListener("pointerdown", handlePointerDown);
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [close, open, triggerEl, updatePosition]);

  const triggerNode = trigger({
    open,
    toggle,
    close,
    setTriggerRef: setTriggerEl,
  });

  return (
    <span className={cn("nodedc-ui-dropdown", className)}>
      {triggerNode}
      {open && typeof document !== "undefined"
        ? createPortal(
            <div
              className={cn("nodedc-ui-dropdown-surface nodedc-dropdown-surface", surfaceClassName)}
              data-nodedc-dropdown-surface="true"
              style={style}
            >
              {typeof children === "function" ? children({ close }) : children}
            </div>,
            document.body
          )
        : null}
    </span>
  );
}
