"use client";

import { useId, useLayoutEffect, useRef, type ReactNode } from "react";
import { createPortal } from "react-dom";
import { MoreVertical } from "lucide-react";

interface ActionMenuProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  label: string;
  children: ReactNode;
}

export function ActionMenu({ open, onOpenChange, label, children }: ActionMenuProps) {
  const id = useId();
  const triggerRef = useRef<HTMLButtonElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);

  useLayoutEffect(() => {
    if (!open) return;
    const trigger = triggerRef.current;
    const menu = menuRef.current;
    if (!trigger || !menu) return;

    const position = () => {
      const rect = trigger.getBoundingClientRect();
      const viewport = window.visualViewport;
      const leftEdge = (viewport?.offsetLeft ?? 0) + 8;
      const topEdge = (viewport?.offsetTop ?? 0) + 8;
      const rightEdge = leftEdge + (viewport?.width ?? window.innerWidth) - 16;
      const bottomEdge = topEdge + (viewport?.height ?? window.innerHeight) - 16;
      if (rect.bottom < topEdge || rect.top > bottomEdge || rect.right < leftEdge || rect.left > rightEdge) {
        onOpenChange(false);
        return;
      }
      menu.style.width = `${Math.min(192, rightEdge - leftEdge)}px`;
      const below = Math.max(0, bottomEdge - rect.bottom - 4);
      const above = Math.max(0, rect.top - topEdge - 4);
      const upwards = menu.scrollHeight > below && above > below;
      menu.style.maxHeight = `${upwards ? above : below}px`;
      const height = menu.getBoundingClientRect().height;
      menu.style.top = `${Math.max(topEdge, Math.min(bottomEdge - height, upwards ? rect.top - height - 4 : rect.bottom + 4))}px`;
      menu.style.left = `${Math.max(leftEdge, Math.min(rect.right - menu.offsetWidth, rightEdge - menu.offsetWidth))}px`;
      menu.style.visibility = "visible";
    };
    const buttons = () => Array.from(menu.querySelectorAll<HTMLButtonElement>("button:not(:disabled)"));
    const close = (restoreFocus: boolean) => {
      onOpenChange(false);
      if (restoreFocus) trigger.focus();
    };
    const onPointerDown = (event: PointerEvent) => {
      if (!menu.contains(event.target as Node) && !trigger.contains(event.target as Node)) close(false);
    };
    const onFocus = (event: FocusEvent) => {
      if (!menu.contains(event.target as Node) && !trigger.contains(event.target as Node)) close(false);
    };
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        event.preventDefault();
        close(true);
      } else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
        event.preventDefault();
        const items = buttons();
        const current = items.indexOf(document.activeElement as HTMLButtonElement);
        const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1
          : (current + (event.key === "ArrowDown" ? 1 : -1) + items.length) % items.length;
        items[next]?.focus();
      } else if (event.key === "Tab") {
        trigger.focus();
        close(false);
      }
    };
    position();
    buttons().forEach(button => button.setAttribute("role", "menuitem"));
    buttons()[0]?.focus({ preventScroll: true });
    const observer = new ResizeObserver(position);
    observer.observe(menu);
    observer.observe(trigger);
    window.addEventListener("resize", position);
    window.addEventListener("scroll", position, true);
    window.visualViewport?.addEventListener("resize", position);
    window.visualViewport?.addEventListener("scroll", position);
    document.addEventListener("pointerdown", onPointerDown);
    document.addEventListener("focusin", onFocus);
    document.addEventListener("keydown", onKeyDown);
    return () => {
      observer.disconnect();
      window.removeEventListener("resize", position);
      window.removeEventListener("scroll", position, true);
      window.visualViewport?.removeEventListener("resize", position);
      window.visualViewport?.removeEventListener("scroll", position);
      document.removeEventListener("pointerdown", onPointerDown);
      document.removeEventListener("focusin", onFocus);
      document.removeEventListener("keydown", onKeyDown);
    };
  }, [open, onOpenChange]);

  return (
    <>
      <button
        ref={triggerRef}
        type="button"
        aria-label={label}
        aria-haspopup="menu"
        aria-expanded={open}
        aria-controls={open ? id : undefined}
        onClick={() => onOpenChange(!open)}
        onKeyDown={event => {
          if (event.key === "ArrowDown" || event.key === "ArrowUp") {
            event.preventDefault();
            onOpenChange(true);
          }
        }}
        className="shrink-0 rounded-lg p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 focus-visible:outline-2 focus-visible:outline-navy-500"
      >
        <MoreVertical className="h-4 w-4" />
      </button>
      {open && createPortal(
        <div
          id={id}
          ref={menuRef}
          role="menu"
          aria-label={label}
          style={{ visibility: "hidden" }}
          className="fixed z-[80] w-48 overflow-y-auto overscroll-contain rounded-lg border border-gray-200 bg-white py-1 text-left shadow-lg [&_button]:min-h-9 [&_button]:text-left [&_button]:whitespace-normal [&_button]:break-words [&_button:focus-visible]:bg-gray-100 [&_button:focus-visible]:outline-none [&_svg]:shrink-0"
        >
          {children}
        </div>, document.body
      )}
    </>
  );
}
