"use client";

import * as React from "react";
import { createPortal } from "react-dom";
import { Check, ChevronDown } from "lucide-react";

import { cn } from "@/lib/utils";

export type SelectOption = {
  value: string;
  label: React.ReactNode;
  disabled?: boolean;
};

type SelectPosition = {
  left: number;
  maxHeight: number;
  top: number;
  width: number;
};

type SelectProps = {
  "aria-describedby"?: string;
  "aria-label"?: string;
  "aria-labelledby"?: string;
  className?: string;
  defaultValue?: string;
  disabled?: boolean;
  id?: string;
  label?: React.ReactNode;
  name?: string;
  onBlur?: React.FocusEventHandler<HTMLButtonElement>;
  onFocus?: React.FocusEventHandler<HTMLButtonElement>;
  onValueChange?: (value: string) => void;
  options: SelectOption[];
  placeholder?: string;
  required?: boolean;
  value?: string;
};

const SELECT_GAP = 6;
const SELECT_MAX_HEIGHT = 288;
const VIEWPORT_PADDING = 8;

function getEnabledValues(options: SelectOption[]): string[] {
  return options.filter((option) => option.disabled !== true).map((option) => option.value);
}

function getInitialValue(options: SelectOption[], defaultValue?: string, placeholder?: string): string {
  if (defaultValue !== undefined) {
    return defaultValue;
  }

  if (placeholder !== undefined) {
    return "";
  }

  return getEnabledValues(options)[0] ?? "";
}

function getAdjacentValue(options: SelectOption[], currentValue: string, direction: 1 | -1): string {
  const enabledValues = getEnabledValues(options);

  if (enabledValues.length === 0) {
    return currentValue;
  }

  const currentIndex = enabledValues.indexOf(currentValue);
  const nextIndex =
    currentIndex === -1
      ? direction === 1
        ? 0
        : enabledValues.length - 1
      : (currentIndex + direction + enabledValues.length) % enabledValues.length;

  return enabledValues[nextIndex] ?? currentValue;
}

function getBoundaryValue(options: SelectOption[], boundary: "first" | "last", fallbackValue: string): string {
  const enabledValues = getEnabledValues(options);

  if (enabledValues.length === 0) {
    return fallbackValue;
  }

  if (boundary === "first") {
    return enabledValues[0] ?? fallbackValue;
  }

  return enabledValues[enabledValues.length - 1] ?? fallbackValue;
}

function findOption(options: SelectOption[], value: string): SelectOption | undefined {
  return options.find((option) => option.value === value);
}

export function Select({
  "aria-describedby": ariaDescribedBy,
  "aria-label": ariaLabel,
  "aria-labelledby": ariaLabelledBy,
  className,
  defaultValue,
  disabled = false,
  id,
  label,
  name,
  onBlur,
  onFocus,
  onValueChange,
  options,
  placeholder,
  required,
  value,
}: SelectProps): React.ReactElement {
  const generatedId = React.useId().replace(/:/g, "");
  const controlId = id ?? `teatea-select-${generatedId}`;
  const labelId = label ? `${controlId}-label` : undefined;
  const listboxId = `${controlId}-listbox`;
  const triggerRef = React.useRef<HTMLButtonElement>(null);
  const listboxRef = React.useRef<HTMLDivElement>(null);
  const [portalRoot, setPortalRoot] = React.useState<HTMLElement | null>(null);
  const [isOpen, setIsOpen] = React.useState(false);
  const [position, setPosition] = React.useState<SelectPosition | null>(null);
  const [uncontrolledValue, setUncontrolledValue] = React.useState(() =>
    getInitialValue(options, defaultValue, placeholder),
  );
  const isControlled = value !== undefined;
  const selectedValue = isControlled ? value : uncontrolledValue;
  const selectedOption = findOption(options, selectedValue);
  const [highlightedValue, setHighlightedValue] = React.useState(selectedValue);
  const highlightedIndex = options.findIndex((option) => option.value === highlightedValue);
  const activeDescendant =
    isOpen && highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined;

  const updatePosition = React.useCallback(() => {
    const trigger = triggerRef.current;

    if (!trigger || typeof window === "undefined") {
      return;
    }

    const rect = trigger.getBoundingClientRect();
    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;
    const availableBelow = viewportHeight - rect.bottom - SELECT_GAP - VIEWPORT_PADDING;
    const availableAbove = rect.top - SELECT_GAP - VIEWPORT_PADDING;
    const estimatedHeight = Math.min(SELECT_MAX_HEIGHT, Math.max(120, options.length * 40 + 12));
    const shouldOpenBelow = availableBelow >= estimatedHeight || availableBelow >= availableAbove;
    const availableHeight = shouldOpenBelow ? availableBelow : availableAbove;
    const maxHeight = Math.max(96, Math.min(SELECT_MAX_HEIGHT, availableHeight));
    const left = Math.min(
      Math.max(VIEWPORT_PADDING, rect.left),
      Math.max(VIEWPORT_PADDING, viewportWidth - rect.width - VIEWPORT_PADDING),
    );
    const top = shouldOpenBelow
      ? Math.min(rect.bottom + SELECT_GAP, viewportHeight - maxHeight - VIEWPORT_PADDING)
      : Math.max(VIEWPORT_PADDING, rect.top - SELECT_GAP - maxHeight);

    setPosition({
      left,
      maxHeight,
      top,
      width: rect.width,
    });
  }, [options.length]);

  const openSelect = React.useCallback(() => {
    if (disabled || options.length === 0) {
      return;
    }

    const nextHighlightedValue =
      findOption(options, selectedValue)?.disabled === true
        ? getBoundaryValue(options, "first", selectedValue)
        : selectedValue;

    setHighlightedValue(nextHighlightedValue);
    setIsOpen(true);
  }, [disabled, options, selectedValue]);

  const closeSelect = React.useCallback(() => {
    setIsOpen(false);
  }, []);

  const commitValue = React.useCallback(
    (nextValue: string) => {
      const nextOption = findOption(options, nextValue);

      if (!nextOption || nextOption.disabled === true) {
        return;
      }

      if (!isControlled) {
        setUncontrolledValue(nextValue);
      }

      if (nextValue !== selectedValue) {
        onValueChange?.(nextValue);
      }

      setHighlightedValue(nextValue);
      closeSelect();
      triggerRef.current?.focus();
    },
    [closeSelect, isControlled, onValueChange, options, selectedValue],
  );

  React.useEffect(() => {
    setPortalRoot(document.body);
  }, []);

  React.useEffect(() => {
    if (isControlled) {
      return;
    }

    if (findOption(options, uncontrolledValue)) {
      return;
    }

    setUncontrolledValue(getInitialValue(options, defaultValue, placeholder));
  }, [defaultValue, isControlled, options, placeholder, uncontrolledValue]);

  React.useEffect(() => {
    const trigger = triggerRef.current;
    const form = trigger?.form;

    if (!form || isControlled) {
      return;
    }

    function handleReset(): void {
      setUncontrolledValue(getInitialValue(options, defaultValue, placeholder));
    }

    form.addEventListener("reset", handleReset);

    return () => {
      form.removeEventListener("reset", handleReset);
    };
  }, [defaultValue, isControlled, options, placeholder]);

  React.useLayoutEffect(() => {
    if (!isOpen) {
      return;
    }

    updatePosition();
  }, [isOpen, updatePosition]);

  React.useEffect(() => {
    if (!isOpen) {
      return;
    }

    function handlePointerDown(event: MouseEvent | TouchEvent): void {
      const target = event.target;

      if (!(target instanceof Node)) {
        return;
      }

      if (triggerRef.current?.contains(target) || listboxRef.current?.contains(target)) {
        return;
      }

      closeSelect();
    }

    document.addEventListener("mousedown", handlePointerDown);
    document.addEventListener("touchstart", handlePointerDown);
    window.addEventListener("resize", updatePosition);
    window.addEventListener("scroll", updatePosition, true);

    return () => {
      document.removeEventListener("mousedown", handlePointerDown);
      document.removeEventListener("touchstart", handlePointerDown);
      window.removeEventListener("resize", updatePosition);
      window.removeEventListener("scroll", updatePosition, true);
    };
  }, [closeSelect, isOpen, updatePosition]);

  function handleTriggerClick(): void {
    if (isOpen) {
      closeSelect();
      return;
    }

    openSelect();
  }

  function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>): void {
    switch (event.key) {
      case "ArrowDown": {
        event.preventDefault();
        if (!isOpen) {
          openSelect();
          return;
        }
        setHighlightedValue((currentValue) => getAdjacentValue(options, currentValue, 1));
        return;
      }
      case "ArrowUp": {
        event.preventDefault();
        if (!isOpen) {
          openSelect();
          return;
        }
        setHighlightedValue((currentValue) => getAdjacentValue(options, currentValue, -1));
        return;
      }
      case "Home": {
        if (!isOpen) {
          return;
        }
        event.preventDefault();
        setHighlightedValue((currentValue) => getBoundaryValue(options, "first", currentValue));
        return;
      }
      case "End": {
        if (!isOpen) {
          return;
        }
        event.preventDefault();
        setHighlightedValue((currentValue) => getBoundaryValue(options, "last", currentValue));
        return;
      }
      case "Enter":
      case " ": {
        event.preventDefault();
        if (!isOpen) {
          openSelect();
          return;
        }
        commitValue(highlightedValue);
        return;
      }
      case "Escape": {
        if (!isOpen) {
          return;
        }
        event.preventDefault();
        closeSelect();
        return;
      }
      case "Tab": {
        closeSelect();
        return;
      }
      default:
        return;
    }
  }

  const field = (
    <div className="grid gap-2">
      {label ? (
        <span id={labelId} className="text-sm font-medium text-foreground">
          {label}
        </span>
      ) : null}
      <button
        ref={triggerRef}
        aria-activedescendant={activeDescendant}
        aria-controls={isOpen ? listboxId : undefined}
        aria-describedby={ariaDescribedBy}
        aria-expanded={isOpen}
        aria-haspopup="listbox"
        aria-label={labelId ? undefined : ariaLabel}
        aria-labelledby={ariaLabelledBy ?? labelId}
        aria-required={required}
        className={cn("teatea-select-trigger", className)}
        data-open={isOpen ? "true" : undefined}
        disabled={disabled}
        id={controlId}
        onBlur={onBlur}
        onClick={handleTriggerClick}
        onFocus={onFocus}
        onKeyDown={handleKeyDown}
        role="combobox"
        type="button"
      >
        <span className={cn("min-w-0 truncate", !selectedOption && "text-muted-foreground")}>
          {selectedOption?.label ?? placeholder ?? ""}
        </span>
        <ChevronDown
          aria-hidden="true"
          className={cn("size-4 shrink-0 text-primary transition-transform", isOpen && "rotate-180")}
        />
      </button>
      {name ? <input name={name} type="hidden" value={selectedValue} /> : null}
    </div>
  );

  const popup =
    portalRoot && isOpen && position
      ? createPortal(
          <div
            ref={listboxRef}
            aria-labelledby={ariaLabelledBy ?? labelId}
            className="teatea-select-panel"
            id={listboxId}
            role="listbox"
            style={{
              left: position.left,
              maxHeight: position.maxHeight,
              top: position.top,
              width: position.width,
            }}
          >
            {options.map((option, index) => {
              const isSelected = option.value === selectedValue;
              const isHighlighted = option.value === highlightedValue;

              return (
                <button
                  aria-disabled={option.disabled}
                  aria-selected={isSelected}
                  className="teatea-select-option"
                  data-highlighted={isHighlighted ? "true" : undefined}
                  data-selected={isSelected ? "true" : undefined}
                  disabled={option.disabled}
                  id={`${listboxId}-option-${index}`}
                  key={option.value}
                  onClick={() => commitValue(option.value)}
                  onMouseDown={(event) => event.preventDefault()}
                  onMouseEnter={() => {
                    if (option.disabled !== true) {
                      setHighlightedValue(option.value);
                    }
                  }}
                  role="option"
                  type="button"
                >
                  <span className="min-w-0 truncate">{option.label}</span>
                  {isSelected ? <Check aria-hidden="true" className="size-4 shrink-0 text-primary" /> : null}
                </button>
              );
            })}
          </div>,
          portalRoot,
        )
      : null;

  return (
    <>
      {field}
      {popup}
    </>
  );
}
