import { ChevronDownIcon, ErrorIcon } from '@trail-ui/icons';
import { clsx } from '@trail-ui/shared-utils';
import {
  type SlotsToClasses,
  type SelectComboBoxSlots,
  selectComboBox,
  selectComboboxItem,
} from '@trail-ui/theme';
import {
  type ForwardedRef,
  forwardRef,
  type ReactElement,
  type ReactNode,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import {
  Button,
  ComboBox,
  type ComboBoxProps,
  Input,
  Label,
  Popover,
  Text,
  ListBox,
  ListBoxItem,
} from 'react-aria-components';
import { ListBoxSelectedIcon } from '../listbox/listbox-selected-icon';
import type { ListBoxItemProps } from '../listbox';
import { InternalListBoxContext } from '../listbox/listbox';
import { replaceSpacesWithHyphens } from '../_utils/utils';

export interface SelectComboboxProps<T extends object> extends Omit<ComboBoxProps<T>, 'children'> {
  /**
   * The label of the select combobox
   */
  label?: string;
  /**
   * The placeholder of the select combobox
   */
  placeholder?: string;
  /**
   * The id of the error message
   */
  errorId?: string;
  /**
   * The error icon of the select combobox
   */
  errorIcon?: ReactNode;
  /**
   * The error message of the select combobox
   */
  errorMessage?: string;
  /**
   * The items of the select combobox
   */
  children: ReactNode | ((item: T) => ReactNode);
  /**
   * The classname of slots of the select combobox
   */
  classNames?: SlotsToClasses<SelectComboBoxSlots>;
  /**
   * The current ListBox item that has focus
   */
  currentFocusedItem?: string;
}

function SelectCombobox<T extends object>(
  {
    label,
    errorId,
    errorIcon = (
      <ErrorIcon
        className="h-4 w-4 text-red-800 dark:text-red-600"
        role="img"
        aria-label="Error"
        aria-hidden={false}
      />
    ),
    errorMessage,
    children,
    className,
    classNames,
    placeholder,
    items,
    ...props
  }: SelectComboboxProps<T>,
  ref: ForwardedRef<HTMLDivElement>,
) {
  const baseStyles = clsx(classNames?.base, className);
  const slots = useMemo(() => selectComboBox(), []);
  const targetRef = useRef<HTMLDivElement>(null);
  const [isOpen, setIsOpen] = useState<boolean>(false);
  const inputRef = useRef<HTMLInputElement>(null);

  // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.setAttribute(
        'aria-activedescendant',
        (props.selectedKey as string)
          ? `${label ? `${replaceSpacesWithHyphens(label)}-` : ''}listbox-option-${String(props.selectedKey)?.toLocaleLowerCase()}`
          : '',
      );
    }
  }, [isOpen]);

  return (
    <>
      <ComboBox
        {...props}
        ref={ref}
        className={slots.base({
          className: baseStyles,
        })}
        onOpenChange={setIsOpen}
      >
        {label && (
          <Label
            className={slots.label({ class: classNames?.label })}
            id={`${label ? replaceSpacesWithHyphens(label) : 'combobox'}-label`}
          >
            {label}
          </Label>
        )}
        <div
          className={slots.inputWrapper({
            class: classNames?.inputWrapper,
          })}
        >
          <Input
            ref={inputRef}
            placeholder={placeholder}
            aria-autocomplete="list"
            aria-describedby={errorId}
            aria-haspopup="listbox"
            aria-controls={label ? `${replaceSpacesWithHyphens(label)}-listbox` : ''}
            className={slots.input({
              class: classNames?.input,
            })}
            {...(props.isRequired && { 'aria-required': true })}
            aria-activedescendant={
              props.currentFocusedItem
                ? `${label ? `${replaceSpacesWithHyphens(label)}-` : ''}listbox-option-${String(props.currentFocusedItem).toLocaleLowerCase()}`
                : ''
            }
          />
          <Button
            excludeFromTabOrder
            className={`${slots.inputTrigger({ class: classNames?.inputTrigger })}`}
            aria-label={label ? `${label} Options` : ''}
          >
            <ChevronDownIcon height={24} width={24} className="text-neutral-800" />
          </Button>
        </div>
        {errorMessage && (
          <Text
            id={errorId}
            slot="errorMessage"
            aria-live="polite"
            elementType="div"
            className={`${slots.errorMessage({ class: classNames?.errorMessage })}`}
          >
            {errorIcon}
            <span>{errorMessage}</span>
          </Text>
        )}
        <div ref={targetRef} />
        <Popover
          className={slots.popover({ class: classNames?.popover })}
          UNSTABLE_portalContainer={targetRef.current || undefined}
        >
          <ListBox
            id={label ? `${replaceSpacesWithHyphens(label)}-listbox` : ''}
            aria-label={label}
            className={slots.listBox({ class: classNames?.listBox })}
            // biome-ignore lint/a11y/useValidAriaValues: need to pass empty string to clear aria-labelledby property
            aria-labelledby=""
          >
            {children}
          </ListBox>
        </Popover>
      </ComboBox>
    </>
  );
}

function SelectComboboxItem(props: ListBoxItemProps & { children: React.ReactNode }) {
  const context = useContext(InternalListBoxContext);

  const {
    children,
    description,
    startContent,
    endContent,
    selectedIcon,
    className,
    classNames = context.itemClasses,
    ...otherProps
  } = props;

  const baseStyles = clsx(classNames?.base, className);
  const slots = useMemo(() => selectComboboxItem(), []);

  return (
    <ListBoxItem {...otherProps} className={slots.base({ class: baseStyles })}>
      {({ isSelected, isDisabled, selectionMode, isFocused }) => {
        if (isFocused && props.textValue && props.onFocusChange) {
          props.onFocusChange(props.textValue);
        }
        const selectedContent = () => {
          const defaultIcon = <ListBoxSelectedIcon isSelected={isSelected} />;

          if (typeof selectedIcon === 'function') {
            return selectedIcon({ icon: defaultIcon, isSelected, isDisabled });
          }

          if (selectedIcon) return selectedIcon;

          return defaultIcon;
        };

        return (
          <>
            {startContent}
            {description ? (
              <div className={slots.wrapper({ class: classNames?.wrapper })}>
                <Text slot="label" className={slots.title({ class: classNames?.title })}>
                  {children}
                </Text>
                <Text
                  slot="description"
                  className={slots.description({
                    class: classNames?.description,
                  })}
                >
                  {description}
                </Text>
              </div>
            ) : (
              <Text slot="label" className={slots.title({ class: classNames?.title })}>
                {children}
              </Text>
            )}
            {isSelected && selectionMode !== 'none' && (
              <span
                aria-hidden="true"
                className={slots.selectedIcon({
                  class: classNames?.selectedIcon,
                })}
              >
                {selectedContent()}
              </span>
            )}
            {endContent}
          </>
        );
      }}
    </ListBoxItem>
  );
}

const _SelectCombobox = forwardRef(SelectCombobox) as <T extends object>(
  props: SelectComboboxProps<T> & { ref?: ForwardedRef<HTMLDivElement> },
) => ReactElement;

export { _SelectCombobox as SelectCombobox, SelectComboboxItem };
