import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";

import { cn, zIndex } from "@/shared";

export interface TicketViewportSelectOption {
  value: string;
  label: string;
  disabled?: boolean;
}

export interface TicketViewportSelectProps {
  ariaLabel?: string;
  "aria-label"?: string;
  value: string;
  options: TicketViewportSelectOption[];
  onChange: (value: string) => void;
  className?: string;
  disabled?: boolean;
}

interface PopupPosition {
  top: number;
  left: number;
  width: number;
  maxHeight: number;
  placement: "top" | "bottom";
}

const VIEWPORT_MARGIN = 8;
const MIN_POPUP_HEIGHT = 96;
const MAX_POPUP_HEIGHT = 280;

export function TicketViewportSelect({
  ariaLabel: ariaLabelProp,
  "aria-label": ariaLabelAttribute,
  value,
  options,
  onChange,
  className,
  disabled = false,
}: TicketViewportSelectProps) {
  const triggerRef = useRef<HTMLButtonElement | null>(null);
  const popupRef = useRef<HTMLDivElement | null>(null);
  const [open, setOpen] = useState(false);
  const [position, setPosition] = useState<PopupPosition | null>(null);
  const selected = options.find((option) => option.value === value) ?? options[0];
  const ariaLabel = ariaLabelAttribute ?? ariaLabelProp ?? "select option";

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

    const rect = trigger.getBoundingClientRect();
    const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
    const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
    const popupWidth = Math.max(rect.width, 160);
    const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN;
    const spaceAbove = rect.top - VIEWPORT_MARGIN;
    const placement =
      spaceBelow < MIN_POPUP_HEIGHT && spaceAbove > spaceBelow ? "top" : "bottom";
    const available = Math.max(
      MIN_POPUP_HEIGHT,
      placement === "top" ? spaceAbove : spaceBelow,
    );
    const maxHeight = Math.min(MAX_POPUP_HEIGHT, available);
    const left = Math.min(
      Math.max(VIEWPORT_MARGIN, rect.left),
      Math.max(VIEWPORT_MARGIN, viewportWidth - popupWidth - VIEWPORT_MARGIN),
    );
    const top =
      placement === "top"
        ? Math.max(VIEWPORT_MARGIN, rect.top - maxHeight - 4)
        : Math.min(viewportHeight - VIEWPORT_MARGIN, rect.bottom + 4);

    setPosition({ top, left, width: popupWidth, maxHeight, placement });
  }, []);

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

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

    function onPointerDown(event: PointerEvent) {
      const target = event.target as Node | null;
      if (
        target &&
        (triggerRef.current?.contains(target) || popupRef.current?.contains(target))
      ) {
        return;
      }
      setOpen(false);
    }

    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") {
        event.preventDefault();
        setOpen(false);
        triggerRef.current?.focus();
      }
    }

    const onViewportChange = () => {
      updatePosition();
    };

    document.addEventListener("pointerdown", onPointerDown);
    document.addEventListener("keydown", onKeyDown);
    window.addEventListener("resize", onViewportChange);
    window.addEventListener("scroll", onViewportChange, true);
    window.visualViewport?.addEventListener("resize", onViewportChange);
    window.visualViewport?.addEventListener("scroll", onViewportChange);

    return () => {
      document.removeEventListener("pointerdown", onPointerDown);
      document.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("resize", onViewportChange);
      window.removeEventListener("scroll", onViewportChange, true);
      window.visualViewport?.removeEventListener("resize", onViewportChange);
      window.visualViewport?.removeEventListener("scroll", onViewportChange);
    };
  }, [open, updatePosition]);

  return (
    <>
      <button
        ref={triggerRef}
        type="button"
        aria-label={ariaLabel}
        aria-haspopup="listbox"
        aria-expanded={open}
        disabled={disabled}
        onClick={() => setOpen((current) => !current)}
        className={cn(
          "inline-flex h-8 items-center justify-between gap-2 rounded-md bg-raised px-2 text-xs text-content",
          "border border-border outline-none transition-colors",
          "focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
          className,
        )}
      >
        <span className="min-w-0 truncate">{selected?.label ?? ""}</span>
        <span aria-hidden="true" className="text-muted">▾</span>
      </button>
      {open &&
        position &&
        createPortal(
          <div
            ref={popupRef}
            role="listbox"
            aria-label={ariaLabel}
            data-placement={position.placement}
            className="fixed overflow-y-auto rounded-md border border-border bg-surface py-1 shadow-xl"
            style={{
              top: position.top,
              left: position.left,
              width: position.width,
              maxHeight: position.maxHeight,
              zIndex: zIndex.menuDropdown,
            }}
          >
            {options.map((option) => (
              <button
                key={option.value}
                type="button"
                role="option"
                aria-selected={option.value === value}
                disabled={option.disabled}
                onClick={() => {
                  if (option.disabled) return;
                  onChange(option.value);
                  setOpen(false);
                  triggerRef.current?.focus();
                }}
                className={cn(
                  "flex min-h-8 w-full items-center px-2 text-left text-xs text-content",
                  "hover:bg-raised focus:bg-raised focus:outline-none",
                  option.value === value && "bg-raised",
                  option.disabled && "cursor-not-allowed opacity-50",
                )}
              >
                <span className="min-w-0 truncate">{option.label}</span>
              </button>
            ))}
          </div>,
          document.body,
        )}
    </>
  );
}
