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

import { cn } from "@/shared/lib/cn";
import { zIndex } from "./zIndex";

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

export interface SmallDropdownProps {
  ariaLabel?: string;
  "aria-label"?: string;
  value: string;
  options: SmallDropdownOption[];
  onChange: (value: string) => void;
  id?: string;
  className?: string;
  disabled?: boolean;
  size?: "sm" | "md";
  invalid?: 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;

function firstEnabledIndex(options: SmallDropdownOption[]): number {
  return options.findIndex((option) => !option.disabled);
}

function nextEnabledIndex(
  options: SmallDropdownOption[],
  from: number,
  direction: 1 | -1,
): number {
  if (options.length === 0) return -1;
  for (let offset = 1; offset <= options.length; offset += 1) {
    const index = (from + offset * direction + options.length) % options.length;
    if (!options[index]?.disabled) return index;
  }
  return -1;
}

export function SmallDropdown({
  ariaLabel: ariaLabelProp,
  "aria-label": ariaLabelAttribute,
  value,
  options,
  onChange,
  id,
  className,
  disabled = false,
  size = "sm",
  invalid = false,
}: SmallDropdownProps) {
  const idBase = useId();
  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 selectedIndex = options.findIndex((option) => option.value === value);
  const selected = selectedIndex >= 0 ? options[selectedIndex] : options[0];
  const [activeIndex, setActiveIndex] = useState(
    selectedIndex >= 0 ? selectedIndex : firstEnabledIndex(options),
  );
  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 });
  }, []);

  const openPopup = useCallback(() => {
    if (disabled) return;
    const nextActive = selectedIndex >= 0 ? selectedIndex : firstEnabledIndex(options);
    setActiveIndex(nextActive);
    setOpen(true);
  }, [disabled, options, selectedIndex]);

  const closePopup = useCallback(() => {
    setOpen(false);
  }, []);

  const selectOption = useCallback(
    (option: SmallDropdownOption | undefined) => {
      if (!option || option.disabled) return;
      onChange(option.value);
      closePopup();
      triggerRef.current?.focus();
    },
    [closePopup, onChange],
  );

  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;
      }
      closePopup();
    }

    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") {
        event.preventDefault();
        closePopup();
        triggerRef.current?.focus();
        return;
      }
      if (event.key === "ArrowDown" || event.key === "ArrowUp") {
        event.preventDefault();
        setActiveIndex((current) =>
          nextEnabledIndex(options, current, event.key === "ArrowDown" ? 1 : -1),
        );
        return;
      }
      if (event.key === "Home") {
        event.preventDefault();
        setActiveIndex(firstEnabledIndex(options));
        return;
      }
      if (event.key === "End") {
        event.preventDefault();
        setActiveIndex(nextEnabledIndex(options, 0, -1));
        return;
      }
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        selectOption(options[activeIndex]);
      }
    }

    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);
    };
  }, [activeIndex, closePopup, open, options, selectOption, updatePosition]);

  const popupId = `${idBase}-listbox`;
  const activeOptionId =
    open && activeIndex >= 0 ? `${idBase}-option-${activeIndex}` : undefined;

  return (
    <>
      <button
        ref={triggerRef}
        id={id}
        type="button"
        aria-label={ariaLabel}
        aria-haspopup="listbox"
        aria-expanded={open}
        aria-controls={open ? popupId : undefined}
        aria-activedescendant={activeOptionId}
        aria-invalid={invalid ? "true" : undefined}
        disabled={disabled}
        onClick={() => (open ? closePopup() : openPopup())}
        onKeyDown={(event) => {
          if (
            !open &&
            (event.key === "ArrowDown" ||
              event.key === "ArrowUp" ||
              event.key === "Enter" ||
              event.key === " ")
          ) {
            event.preventDefault();
            openPopup();
          }
        }}
        className={cn(
          "inline-flex items-center justify-between gap-2 rounded-md bg-raised text-content",
          "border outline-none transition-colors",
          "focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
          invalid ? "border-danger" : "border-border",
          size === "md" ? "h-9 px-3 text-sm" : "h-8 px-2 text-xs",
          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}
            id={popupId}
            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.transientPopup,
            }}
          >
            {options.map((option, index) => (
              <button
                key={`${option.value}-${index}`}
                id={`${idBase}-option-${index}`}
                type="button"
                role="option"
                aria-selected={option.value === value}
                disabled={option.disabled}
                onMouseEnter={() => {
                  if (!option.disabled) setActiveIndex(index);
                }}
                onClick={() => selectOption(option)}
                className={cn(
                  "flex min-h-8 w-full items-center px-2 text-left text-content",
                  "hover:bg-raised focus:bg-raised focus:outline-none",
                  size === "md" ? "text-sm" : "text-xs",
                  (option.value === value || index === activeIndex) && "bg-raised",
                  option.disabled && "cursor-not-allowed opacity-50",
                )}
              >
                <span className="min-w-0 truncate">{option.label}</span>
              </button>
            ))}
          </div>,
          document.body,
        )}
    </>
  );
}
