import { Listbox as ListboxBase } from '@headlessui/react';
import { Check, ChevronDown, Cross, CrossCircle } from '@transferwise/icons';
import { clsx } from 'clsx';
import mergeProps from 'merge-props';
import {
  createContext,
  forwardRef,
  useContext,
  useDeferredValue,
  useEffect,
  useId,
  useMemo,
  useRef,
  useState,
} from 'react';
import { useIntl } from 'react-intl';
import { Virtualizer } from 'virtua';

import { useEffectEvent } from '../common/hooks/useEffectEvent';
import { useScreenSize } from '../common/hooks/useScreenSize';
import { PolymorphicWithOverrides } from '../common/polymorphicWithOverrides/PolymorphicWithOverrides';
import { Breakpoint } from '../common/propsValues/breakpoint';
import dateTriggerMessages from '../dateLookup/dateTrigger/DateTrigger.messages';
import { Merge } from '../utils';

import { BottomSheet } from './_BottomSheet';
import { ButtonInput } from './_ButtonInput';
import { Popover } from './_Popover';
import { useInputAttributes, WithInputAttributesProps } from './contexts';
import { InputGroup } from './InputGroup';
import { SearchInput } from './SearchInput';
import messages from './SelectInput.messages';

const MAX_ITEMS_WITHOUT_VIRTUALIZATION = 50;

function searchableString(value: string) {
  return value.trim().replace(/\s+/gu, ' ').normalize('NFKC').toLowerCase();
}

function inferSearchableStrings(value: unknown) {
  if (typeof value === 'string') {
    return [searchableString(value)];
  }

  if (typeof value === 'object' && value != null) {
    return Object.values(value)
      .filter((innerValue) => typeof innerValue === 'string')
      .map((innerValue) => searchableString(innerValue));
  }

  return [];
}

export interface SelectInputOptionItem<T = string> {
  type: 'option';
  value: T;
  filterMatchers?: readonly string[];
  disabled?: boolean;
}

export interface SelectInputGroupItem<T = string> {
  type: 'group';
  label: string;
  options: readonly SelectInputOptionItem<T>[];
}

export interface SelectInputSeparatorItem {
  type: 'separator';
}

export type SelectInputItem<T = string> =
  | SelectInputOptionItem<T>
  | SelectInputGroupItem<T>
  | SelectInputSeparatorItem;

function dedupeSelectInputOptionItem<T>(
  item: SelectInputOptionItem<T>,
  existingValues: Set<T>,
): SelectInputOptionItem<T | undefined> {
  if (!existingValues.has(item.value)) {
    existingValues.add(item.value);
    return item;
  }
  return { ...item, value: undefined };
}

/**
 * Sets the `value` of duplicate option items to `undefined`, hiding them when
 * rendered. Indexes are kept intact within groups to preserve the active item
 * between filter changes when possible.
 */
function dedupeSelectInputItems<T>(
  items: readonly SelectInputItem<T>[],
): SelectInputItem<T | undefined>[] {
  const existingValues = new Set<T>();
  return items.map((item) => {
    switch (item.type) {
      case 'option': {
        return dedupeSelectInputOptionItem(item, existingValues);
      }
      case 'group': {
        return {
          ...item,
          options: item.options.map((option) =>
            dedupeSelectInputOptionItem(option, existingValues),
          ),
        };
      }
      default:
    }
    return item;
  });
}

function selectInputOptionItemIncludesNeedle<T>(item: SelectInputOptionItem<T>, needle: string) {
  return inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
    haystack.includes(needle),
  );
}

function filterSelectInputItems<T>(
  items: readonly SelectInputItem<T>[],
  predicate: (item: SelectInputOptionItem<T>) => boolean,
) {
  return items.filter((item) => {
    switch (item.type) {
      case 'option': {
        return predicate(item);
      }
      case 'group': {
        return item.options.some((option) => predicate(option));
      }
      default:
    }
    return false;
  });
}

export interface SelectInputProps<T = string, M extends boolean = false> {
  id?: string;
  name?: string;
  multiple?: M;
  placeholder?: string;
  items: readonly SelectInputItem<NonNullable<T>>[];
  defaultValue?: M extends true ? readonly T[] : T;
  value?: M extends true ? readonly T[] : T;
  compareValues?:
    | (keyof NonNullable<T> & string)
    | ((a: T | undefined, b: T | undefined) => boolean);
  renderValue?: (value: NonNullable<T>, withinTrigger: boolean) => React.ReactNode;
  renderFooter?: (args: {
    resultsEmpty: boolean;
    queryNormalized: string | null | undefined;
  }) => React.ReactNode;
  renderTrigger?: (args: {
    content: React.ReactNode;
    placeholderShown: boolean;
    clear: (() => void) | undefined;
    disabled: boolean;
    size: 'sm' | 'md' | 'lg';
    className: string | undefined;
  }) => React.ReactNode;
  filterable?: boolean;
  filterPlaceholder?: string;
  disabled?: boolean;
  size?: 'sm' | 'md' | 'lg';
  className?: string;
  UNSAFE_triggerButtonProps?: WithInputAttributesProps['inputAttributes'] & {
    'aria-label'?: string;
  };
  onFilterChange?: (args: { query: string; queryNormalized: string | null }) => void;
  onChange?: (value: M extends true ? T[] : T) => void;
  onClose?: () => void;
  onClear?: () => void;
}

const defaultRenderTrigger = (({ content, placeholderShown, clear, disabled, size, className }) => (
  <InputGroup
    addonEnd={{
      content: (
        <span className={clsx('np-select-input-addon-container', disabled && 'disabled')}>
          {clear != null && !placeholderShown ? (
            <>
              <SelectInputClearButton
                onClick={(event) => {
                  event.preventDefault();
                  clear();
                }}
              />
              <span className="np-select-input-addon-separator" />
            </>
          ) : null}

          <span className="np-select-input-addon">
            <ChevronDown size={16} />
          </span>
        </span>
      ),
      initialContentWidth: 24 + 4,
      padding: 'sm',
    }}
    disabled={disabled}
    className={className}
  >
    <SelectInputTriggerButton as={ButtonInput} size={size}>
      <span
        className={clsx(
          'np-select-input-content',
          placeholderShown && 'np-select-input-placeholder',
        )}
      >
        {content}
      </span>
    </SelectInputTriggerButton>
  </InputGroup>
)) satisfies SelectInputProps['renderTrigger'];

interface SelectInputClearButtonProps
  extends Pick<React.ComponentPropsWithoutRef<'button'>, 'className' | 'onClick'> {}

function SelectInputClearButton({ className, onClick }: SelectInputClearButtonProps) {
  const intl = useIntl();

  return (
    <button
      type="button"
      aria-label={intl.formatMessage(dateTriggerMessages.ariaLabel)}
      className={clsx(className, 'np-select-input-addon np-select-input-addon--interactive')}
      onClick={onClick}
    >
      <Cross size={16} />
    </button>
  );
}

const noop = () => {};

export function SelectInput<T = string, M extends boolean = false>({
  id: idProp,
  name,
  multiple,
  placeholder,
  items,
  defaultValue,
  value: controlledValue,
  compareValues,
  renderValue = String,
  renderFooter,
  renderTrigger = defaultRenderTrigger,
  filterable,
  filterPlaceholder,
  disabled,
  size = 'md',
  className,
  UNSAFE_triggerButtonProps,
  onFilterChange = noop,
  onChange,
  onClose,
  onClear,
}: SelectInputProps<T, M>) {
  const inputAttributes = useInputAttributes({ nonLabelable: true });
  const id = idProp ?? inputAttributes.id;

  const [open, setOpen] = useState(false);

  const initialized = useRef(false);
  const handleClose = useEffectEvent(onClose ?? (() => {}));
  useEffect(() => {
    if (initialized.current) {
      if (!open) {
        handleClose?.();
      }
    } else {
      initialized.current = true;
    }
  }, [handleClose, open]);

  const [filterQuery, _setFilterQuery] = useState('');
  const deferredFilterQuery = useDeferredValue(filterQuery);
  const setFilterQuery = useEffectEvent((query: string) => {
    _setFilterQuery(query);
    if (query !== filterQuery) {
      onFilterChange({
        query,
        queryNormalized: query ? searchableString(query) : null,
      });
    }
  });

  const triggerRef = useRef<HTMLButtonElement | null>(null);

  const screenSm = useScreenSize(Breakpoint.SMALL);
  const OptionsOverlay = screenSm ? Popover : BottomSheet;

  const searchInputRef = useRef<HTMLInputElement>(null);
  const listboxRef = useRef<HTMLDivElement>(null);
  const controllerRef = filterable ? searchInputRef : listboxRef;

  /**
   * Attempts to resolve the `listbox` label
   * @see https://storybook.wise.design/?path=/docs/forms-selectinput-accessibility--docs#labelling
   */
  const getListBoxLabelProps = (): {
    listBoxLabel?: string;
    listBoxLabelledBy?: string;
  } => {
    if (UNSAFE_triggerButtonProps?.['aria-label']) {
      return {
        listBoxLabel: UNSAFE_triggerButtonProps['aria-label'],
      };
    }

    if (UNSAFE_triggerButtonProps?.['aria-labelledby']) {
      return {
        listBoxLabelledBy: UNSAFE_triggerButtonProps['aria-labelledby'],
      };
    }

    if (inputAttributes['aria-labelledby']) {
      return {
        listBoxLabelledBy: inputAttributes['aria-labelledby'],
      };
    }

    return {};
  };

  return (
    <ListboxBase
      name={name}
      multiple={multiple}
      defaultValue={defaultValue}
      value={controlledValue}
      by={compareValues}
      disabled={disabled}
      onChange={
        ((value) => {
          if (!multiple) {
            setOpen(false);
          }
          onChange?.(value);
        }) satisfies SelectInputProps<T, M>['onChange']
      }
    >
      {({ disabled: uiDisabled, value }) => {
        const placeholderShown =
          multiple && Array.isArray(value) ? value.length === 0 : value == null;
        return (
          <OptionsOverlay
            placement="bottom-start"
            open={open}
            renderTrigger={({ ref, getInteractionProps }) => (
              <SelectInputTriggerButtonPropsContext.Provider
                // eslint-disable-next-line react/jsx-no-constructed-context-values
                value={{
                  ref: (node) => {
                    ref(node);
                    triggerRef.current = node;
                  },
                  ...inputAttributes,
                  ...UNSAFE_triggerButtonProps,
                  id,
                  ...mergeProps(
                    {
                      onClick: () => {
                        setOpen((prev) => !prev);
                      },
                      onKeyDown: (event: React.KeyboardEvent) => {
                        if (
                          event.key === ' ' ||
                          event.key === 'Enter' ||
                          event.key === 'ArrowDown' ||
                          event.key === 'ArrowUp'
                        ) {
                          setOpen((prev) => !prev);
                        }
                      },
                    },
                    getInteractionProps(),
                  ),
                }}
              >
                {renderTrigger({
                  content: !placeholderShown ? (
                    <SelectInputOptionContentWithinTriggerContext.Provider value>
                      {multiple && Array.isArray(value)
                        ? (value as readonly NonNullable<T>[])
                            .map((option) => renderValue(option, true))
                            .filter((node) => node != null)
                            .join(', ')
                        : renderValue(value as NonNullable<T>, true)}
                    </SelectInputOptionContentWithinTriggerContext.Provider>
                  ) : (
                    placeholder
                  ),
                  placeholderShown,
                  clear:
                    onClear != null
                      ? () => {
                          onClear();
                          triggerRef.current?.focus({ preventScroll: true });
                        }
                      : undefined,
                  disabled: uiDisabled,
                  size,
                  className,
                })}
              </SelectInputTriggerButtonPropsContext.Provider>
            )}
            initialFocusRef={controllerRef}
            size={filterable ? 'lg' : 'md'}
            padding="none"
            onClose={() => {
              setOpen(false);
            }}
            onCloseEnd={() => {
              setFilterQuery('');
            }}
          >
            <SelectInputOptions
              id={id ? `${id}Search` : undefined}
              items={items}
              renderValue={renderValue}
              renderFooter={renderFooter}
              filterable={filterable}
              filterPlaceholder={filterPlaceholder}
              searchInputRef={searchInputRef}
              listboxRef={listboxRef}
              filterQuery={deferredFilterQuery}
              onFilterChange={setFilterQuery}
              {...getListBoxLabelProps()}
            />
          </OptionsOverlay>
        );
      }}
    </ListboxBase>
  );
}

const SelectInputTriggerButtonPropsContext = createContext<{
  ref?: React.ForwardedRef<HTMLButtonElement | null>;
  id?: string;
  onClick?: (event: React.MouseEvent) => void;
  onKeyDown?: (event: React.KeyboardEvent) => void;
  [key: string]: unknown;
}>({});

type SelectInputTriggerButtonElementType = 'button' | React.ComponentType;

export type SelectInputTriggerButtonProps<
  T extends SelectInputTriggerButtonElementType = 'button',
> = Merge<React.ComponentPropsWithoutRef<T>, { as?: T }>;

export function SelectInputTriggerButton<T extends SelectInputTriggerButtonElementType = 'button'>({
  as = 'button' as T,
  ...restProps
}: SelectInputTriggerButtonProps<T>) {
  const { ref, onClick, onKeyDown, ...interactionProps } = useContext(
    SelectInputTriggerButtonPropsContext,
  );

  return (
    <ListboxBase.Button
      ref={ref}
      as={PolymorphicWithOverrides}
      role="combobox"
      __overrides={{ as, ...interactionProps }}
      {...mergeProps({ onClick, onKeyDown }, restProps)}
    />
  );
}

interface SelectInputOptionsContainerProps extends React.ComponentPropsWithRef<'div'> {
  onAriaActiveDescendantChange: (value: React.AriaAttributes['aria-activedescendant']) => void;
}

const SelectInputOptionsContainer = forwardRef(function SelectInputOptionsContainer(
  {
    'aria-orientation': ariaOrientation,
    'aria-activedescendant': ariaActiveDescendant,
    role,
    tabIndex,
    onAriaActiveDescendantChange,
    onKeyDown,
    ...restProps
  }: SelectInputOptionsContainerProps,
  ref: React.ForwardedRef<HTMLDivElement | null>,
) {
  const handleAriaActiveDescendantChange = useEffectEvent(onAriaActiveDescendantChange);
  useEffect(() => {
    handleAriaActiveDescendantChange(ariaActiveDescendant);
  }, [ariaActiveDescendant, handleAriaActiveDescendantChange]);

  return (
    <div
      ref={ref}
      role="none"
      onKeyDown={(event) => {
        // Prevent confirmation close without an active item
        if (event.key === 'Enter' && ariaActiveDescendant == null) {
          return;
        }

        // Prevent absorbing actions early
        if (event.key === 'Escape' || event.key === 'Tab') {
          onKeyDown?.({
            ...event,
            preventDefault: () => {},
            stopPropagation: () => {},
          });
          return;
        }

        onKeyDown?.(event);
      }}
      {...restProps}
    />
  );
});

interface SelectInputOptionsProps<T = string>
  extends Pick<
    SelectInputProps<T>,
    'items' | 'renderValue' | 'renderFooter' | 'filterable' | 'filterPlaceholder' | 'id'
  > {
  searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
  listboxRef: React.MutableRefObject<HTMLDivElement | null>;
  filterQuery: string;
  onFilterChange: (query: string) => void;
  listBoxLabel?: string;
  listBoxLabelledBy?: string;
}

function SelectInputOptions<T = string>({
  id,
  items,
  renderValue = String,
  renderFooter,
  filterable = false,
  filterPlaceholder,
  searchInputRef,
  listboxRef,
  filterQuery,
  onFilterChange,
  listBoxLabel,
  listBoxLabelledBy,
}: SelectInputOptionsProps<T>) {
  const intl = useIntl();
  const controllerRef = filterable ? searchInputRef : listboxRef;
  const [initialRender, setInitialRender] = useState(true);

  const needle = useMemo(() => {
    if (filterable) {
      return filterQuery ? searchableString(filterQuery) : null;
    }
    return undefined;
  }, [filterQuery, filterable]);
  useEffect(() => {
    if (needle) {
      // Ensure having an active option while filtering.
      // Without `requestAnimationFrame` upon which React depends for scheduling
      // updates, the active status would only show for a split second and then
      // disappear inadvertently.
      requestAnimationFrame(() => {
        if (
          controllerRef.current != null &&
          !controllerRef.current.hasAttribute('aria-activedescendant')
        ) {
          // Activate first option via synthetic key press
          controllerRef.current.dispatchEvent(
            new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
          );
        }
      });
    }
  }, [controllerRef, needle]);

  const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] =
    needle != null
      ? filterSelectInputItems(dedupeSelectInputItems(items), (item) =>
          selectInputOptionItemIncludesNeedle(item, needle),
        )
      : items;
  const resultsEmpty = needle != null && filteredItems.length === 0;

  const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;

  // Items shown once shall be kept mounted until the needle changes, otherwise
  // the scroll position may jump around inadvertently. Pattern adopted from:
  // https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
  const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
  useEffect(() => {
    // Ensure the 'End' key works as intended by keeping the last item mounted
    setMountedIndexes((prevMountedIndexes) => {
      const indexes = new Set(prevMountedIndexes);
      indexes.add(filteredItems.length - 1);
      return [...indexes]; // Sorting is redundant by nature here
    });
  }, [
    needle, // Needed as `filteredItems.length` may be equal between two updates
    filteredItems.length,
  ]);

  const listboxContainerRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    if (listboxContainerRef.current != null) {
      listboxContainerRef.current.style.setProperty(
        '--initial-height',
        `${listboxContainerRef.current.offsetHeight}px`,
      );
    }
  }, []);

  useEffect(() => {
    setInitialRender(false);
  }, []);

  const showStatus = resultsEmpty;
  const statusId = useId();
  const listboxId = useId();

  const getItemNode = (index: number) => {
    const item = filteredItems[index];
    return (
      <SelectInputItemView
        // eslint-disable-next-line react/no-array-index-key
        key={index}
        item={item}
        renderValue={renderValue}
        needle={needle}
      />
    );
  };

  return (
    <ListboxBase.Options
      as={SelectInputOptionsContainer}
      static
      className="np-select-input-options-container"
      onAriaActiveDescendantChange={(value: React.AriaAttributes['aria-activedescendant']) => {
        if (controllerRef.current != null) {
          if (!initialRender && value != null) {
            controllerRef.current.setAttribute('aria-activedescendant', value);
          } else {
            controllerRef.current.removeAttribute('aria-activedescendant');
          }
        }
      }}
    >
      {filterable ? (
        <div className="np-select-input-query-container">
          <SearchInput
            ref={searchInputRef}
            id={id}
            role="combobox"
            shape="rectangle"
            placeholder={filterPlaceholder}
            aria-label={filterPlaceholder}
            defaultValue={filterQuery}
            aria-autocomplete="list"
            aria-expanded
            aria-controls={listboxId}
            aria-describedby={showStatus ? statusId : undefined}
            onKeyDown={(event) => {
              // Prevent interfering with the matcher of Headless UI
              // https://mathiasbynens.be/notes/javascript-unicode#regex
              if (/^.$/u.test(event.key)) {
                event.stopPropagation();
              }
            }}
            onChange={(event) => {
              // Free up resources and ensure not to go out of bounds when the
              // resulting item count is less than before
              setMountedIndexes([]);
              onFilterChange(event.currentTarget.value);
            }}
          />
        </div>
      ) : null}

      <section
        ref={listboxContainerRef}
        tabIndex={-1}
        className={clsx(
          'np-select-input-listbox-container',
          virtualized && 'np-select-input-listbox-container--virtualized',
          needle == null && // Groups aren't shown when filtering
            items.some((item) => item.type === 'group') &&
            'np-select-input-listbox-container--has-group',
        )}
      >
        {resultsEmpty ? (
          <div id={statusId} className="np-select-input-options-status">
            <CrossCircle size={16} className="np-select-input-options-status-icon" />
            {intl.formatMessage(messages.noResultsFound)}
          </div>
        ) : null}

        <div
          ref={listboxRef}
          id={listboxId}
          role="listbox"
          aria-orientation="vertical"
          aria-label={listBoxLabel}
          aria-labelledby={listBoxLabelledBy}
          tabIndex={0}
          className="np-select-input-listbox"
        >
          {!virtualized ? (
            filteredItems.map((_, index) => getItemNode(index))
          ) : (
            <Virtualizer
              key={needle}
              count={filteredItems.length}
              keepMounted={mountedIndexes}
              scrollRef={listboxRef} // `VList` doesn't expose this
              onRangeChange={(startIndex, endIndex) => {
                setMountedIndexes((prevMountedIndexes) => {
                  const indexes = new Set(prevMountedIndexes);
                  for (let index = startIndex; index <= endIndex; index += 1) {
                    indexes.add(index);
                  }
                  return [...indexes].sort((a, b) => a - b);
                });
              }}
            >
              {(index) => (
                // The position of each item can't be inferred by browsers when
                // virtualizing, as some of the items may not be in the DOM
                <SelectInputItemsCountContext.Provider value={filteredItems.length}>
                  <SelectInputItemPositionContext.Provider value={index + 1}>
                    {getItemNode(index)}
                  </SelectInputItemPositionContext.Provider>
                </SelectInputItemsCountContext.Provider>
              )}
            </Virtualizer>
          )}
        </div>

        {renderFooter != null ? (
          <footer className="np-select-input-footer">
            <div
              role="none"
              onKeyDown={(event) => {
                // Prevent interfering with Headless UI
                if (event.key !== 'Escape') {
                  event.stopPropagation();
                }
              }}
            >
              {renderFooter({
                resultsEmpty,
                queryNormalized: needle,
              })}
            </div>
          </footer>
        ) : null}
      </section>
    </ListboxBase.Options>
  );
}

interface SelectInputItemViewProps<
  T = string,
  I extends SelectInputItem<T | undefined> = SelectInputItem<T | undefined>,
> extends Required<Pick<SelectInputProps<T>, 'renderValue'>> {
  item: I;
  needle: string | null | undefined;
}

function SelectInputItemView<T = string>({
  item,
  renderValue,
  needle,
}: SelectInputItemViewProps<T>) {
  switch (item.type) {
    case 'option': {
      if (
        item.value != null &&
        (needle == null || selectInputOptionItemIncludesNeedle(item, needle))
      ) {
        return (
          <SelectInputOption value={item.value} disabled={item.disabled}>
            {renderValue(item.value, false)}
          </SelectInputOption>
        );
      }
      break;
    }
    case 'group': {
      return <SelectInputGroupItemView item={item} renderValue={renderValue} needle={needle} />;
    }
    case 'separator': {
      if (needle == null) {
        return <hr className="np-select-input-separator-item" />;
      }
      break;
    }
  }
  return null;
}

interface SelectInputGroupItemViewProps<T = string>
  extends SelectInputItemViewProps<T, SelectInputGroupItem<T | undefined>> {}

function SelectInputGroupItemView<T = string>({
  item,
  renderValue,
  needle,
}: SelectInputGroupItemViewProps<T>) {
  const headerId = useId();

  return (
    // An empty container may be rendered when no options match `needle`
    // However, pre-filtering would result in worse performance overall
    <section
      role="group"
      aria-labelledby={headerId}
      className={clsx(needle === null && 'np-select-input-group-item--without-needle')}
    >
      {needle == null ? (
        <header
          id={headerId}
          role="none"
          className="np-select-input-group-item-header np-text-title-group"
        >
          {item.label}
        </header>
      ) : null}
      {item.options.map((option, index) => (
        <SelectInputItemView
          // eslint-disable-next-line react/no-array-index-key
          key={index}
          item={option}
          renderValue={renderValue}
          needle={needle}
        />
      ))}
    </section>
  );
}

const SelectInputItemsCountContext = createContext<number | undefined>(undefined);
const SelectInputItemPositionContext = createContext<number | undefined>(undefined);

interface SelectInputOptionProps<T = string> {
  value: T;
  disabled?: boolean;
  children?: React.ReactNode;
}

function SelectInputOption<T = string>({ value, disabled, children }: SelectInputOptionProps<T>) {
  const itemsCount = useContext(SelectInputItemsCountContext);
  const itemPosition = useContext(SelectInputItemPositionContext);
  return (
    <ListboxBase.Option
      as="div"
      value={value}
      aria-setsize={itemsCount}
      aria-posinset={itemPosition}
      disabled={disabled}
      className={({ active, disabled: uiDisabled }) =>
        clsx(
          'np-select-input-option-container np-text-body-large',
          active && 'np-select-input-option-container--active',
          uiDisabled && 'np-select-input-option-container--disabled',
        )
      }
    >
      {({ selected }) => (
        <>
          <div className="np-select-input-option">{children}</div>
          <Check
            size={16}
            className={clsx(
              'np-select-input-option-check',
              !selected && 'np-select-input-option-check--not-selected',
            )}
          />
        </>
      )}
    </ListboxBase.Option>
  );
}

const SelectInputOptionContentWithinTriggerContext = createContext(false);

export interface SelectInputOptionContentProps {
  title: string;
  note?: string;
  description?: string;
  icon?: React.ReactNode;
}

export function SelectInputOptionContent({
  title,
  note,
  description,
  icon,
}: SelectInputOptionContentProps) {
  const withinTrigger = useContext(SelectInputOptionContentWithinTriggerContext);

  return (
    <div
      className={clsx(
        'np-select-input-option-content-container',
        (note || description) && 'np-text-body-large',
      )}
    >
      {icon ? (
        <div
          className={clsx(
            'np-select-input-option-content-icon',
            !withinTrigger && 'np-select-input-option-content-icon--not-within-trigger',
          )}
        >
          {icon}
        </div>
      ) : null}

      <div className="np-select-input-option-content-text">
        <div
          className={clsx(
            'np-select-input-option-content-text-line-1',
            withinTrigger && 'np-select-input-option-content-text-within-trigger',
          )}
        >
          <div className="d-inline">{title}</div>
          {note ? (
            <span className="np-select-input-option-content-text-secondary np-text-body-default">
              {note}
            </span>
          ) : null}
        </div>

        {description ? (
          <div
            className={clsx(
              'np-select-input-option-content-text-secondary np-text-body-default',
              withinTrigger && 'np-select-input-option-content-text-within-trigger',
            )}
          >
            {description}
          </div>
        ) : null}
      </div>
    </div>
  );
}
