import {
  useCallback,
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type CSSProperties,
  type ReactNode,
} from "react";
import { createPortal } from "react-dom";
import { computeFloatingPosition, type FloatingPlacement } from "@nodedc/ui-core";
import { cn } from "./cn.js";

export interface DropdownTriggerApi {
  open: boolean;
  show: () => void;
  close: () => void;
  toggle: () => void;
  setAnchorRef: (node: HTMLElement | null) => void;
  setTriggerRef: (node: HTMLElement | null) => void;
  surfaceId: string;
}

export interface DropdownProps {
  trigger: (api: DropdownTriggerApi) => ReactNode;
  children: ReactNode | ((api: { close: () => void }) => ReactNode);
  placement?: FloatingPlacement;
  minWidth?: number;
  width?: number | "anchor";
  offset?: number;
  disabled?: boolean;
  className?: string;
  surfaceClassName?: string;
  surfaceRole?: "menu" | "listbox" | "dialog";
}

export function Dropdown({
  trigger,
  children,
  placement = "bottom-start",
  minWidth = 180,
  width = "anchor",
  offset = 8,
  disabled = false,
  className,
  surfaceClassName,
  surfaceRole = "menu",
}: DropdownProps) {
  const instanceId = useId();
  const surfaceId = `${instanceId.replaceAll(":", "")}-surface`;
  const [isOpen, setIsOpen] = useState(false);
  const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null);
  const [triggerElement, setTriggerElement] = useState<HTMLElement | null>(null);
  const surfaceRef = useRef<HTMLDivElement>(null);
  const [surfaceStyle, setSurfaceStyle] = useState<CSSProperties>({ visibility: "hidden" });

  const close = useCallback(() => setIsOpen(false), []);
  const show = useCallback(() => {
    if (!disabled) setIsOpen(true);
  }, [disabled]);
  const toggle = useCallback(() => {
    if (!disabled) setIsOpen((current) => !current);
  }, [disabled]);

  const updatePosition = useCallback(() => {
    const anchorNode = anchorElement ?? triggerElement;
    if (!anchorNode || !surfaceRef.current) return;
    const anchor = anchorNode.getBoundingClientRect();
    const measured = surfaceRef.current.getBoundingClientRect();
    const surfaceWidth = typeof width === "number"
      ? width
      : Math.max(minWidth, anchor.width, measured.width);
    const surfaceHeight = Math.max(1, surfaceRef.current.scrollHeight);
    const position = computeFloatingPosition({
      anchor,
      surfaceWidth,
      surfaceHeight,
      placement,
      offset,
    });
    setSurfaceStyle({
      top: position.top,
      left: position.left,
      width: surfaceWidth,
      maxHeight: position.maxHeight,
      visibility: "visible",
    });
  }, [anchorElement, minWidth, offset, placement, triggerElement, width]);

  useLayoutEffect(() => {
    if (!isOpen) return;
    updatePosition();
  }, [isOpen, updatePosition]);

  useEffect(() => {
    if (!isOpen) return;
    const handlePointerDown = (event: PointerEvent) => {
      const target = event.target;
      if (!(target instanceof Node)) return;
      if (anchorElement?.contains(target) || triggerElement?.contains(target) || surfaceRef.current?.contains(target)) return;
      close();
    };
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        close();
        event.preventDefault();
        event.stopPropagation();
        triggerElement?.focus();
      }
    };
    const handleOtherOpen = (event: Event) => {
      const detail = (event as CustomEvent<{ id?: string }>).detail;
      if (detail.id !== instanceId) close();
    };
    const handleViewportChange = () => updatePosition();

    window.dispatchEvent(new CustomEvent("nodedc-dropdown-open", { detail: { id: instanceId } }));
    document.addEventListener("pointerdown", handlePointerDown);
    document.addEventListener("keydown", handleKeyDown, true);
    window.addEventListener("nodedc-dropdown-open", handleOtherOpen as EventListener);
    window.addEventListener("resize", handleViewportChange);
    window.addEventListener("scroll", handleViewportChange, true);
    return () => {
      document.removeEventListener("pointerdown", handlePointerDown);
      document.removeEventListener("keydown", handleKeyDown, true);
      window.removeEventListener("nodedc-dropdown-open", handleOtherOpen as EventListener);
      window.removeEventListener("resize", handleViewportChange);
      window.removeEventListener("scroll", handleViewportChange, true);
    };
  }, [anchorElement, close, instanceId, isOpen, triggerElement, updatePosition]);

  return (
    <span className={cn("nodedc-dropdown-anchor", className)}>
      {trigger({ open: isOpen, show, close, toggle, setAnchorRef: setAnchorElement, setTriggerRef: setTriggerElement, surfaceId })}
      {isOpen && typeof document !== "undefined"
        ? createPortal(
            <div
              ref={surfaceRef}
              id={surfaceId}
              className={cn("nodedc-dropdown-surface", surfaceClassName)}
              role={surfaceRole}
              style={surfaceStyle}
            >
              {typeof children === "function" ? children({ close }) : children}
            </div>,
            document.body,
          )
        : null}
    </span>
  );
}
