import { forwardRef, useState } from "react";
import { Flex } from "../flex";
import { Icons } from "../icons";
import { ScrollArea } from "../scroll-area";
import { useInfiniteScroll, useInputDevice } from "../../utils";
import {
  StyledRoot,
  StyledWrapper,
  StyledTrigger,
  StyledFieldSet,
  StyledLegend,
  StyledLabel,
  StyledValue,
  StyledIcon,
  StyledPortal,
  StyledContent,
  StyledViewport,
  StyledGroup,
  StyledItem,
  StyledItemText,
  StyledExtraInfo,
  StyledIconContainer,
  StyledExtraContainer,
} from "./select.styled";
import { useSelect } from "./use-select";

import type { WithTestId } from "../../types";

export interface SelectItem extends React.ComponentProps<typeof StyledItem> {
  value: string;
  label: string;
  extra?: React.ReactNode;
}

export interface SelectProps extends WithTestId<React.ComponentProps<typeof StyledRoot>> {
  /**
   * The aria-label to be added to the select input trigger container
   */
  ariaLabel?: string;
  /**
   * The label text of the select input
   */
  label: string;
  /**
   * The placeholder text of the select input
   */
  placeholder?: string;
  /**
   * The htmlFor text of the select input
   */
  htmlFor?: string;
  /**
   * The options of the select input
   */
  options: Array<SelectItem>;
  /**
   * When set this state to true, the select input will display in red
   */
  error?: boolean;
  /**
   * The extra info to be displayed underneth the select input, for example the error message should be passed to this prop
   */
  info?: string;
  /**
   * If true, will apply ellipsis text overflow to info text
   */
  infoEllipsis?: boolean;
  /**
   * The element to be rendered at the right of the select input
   */
  rightElement?: React.ReactNode;
  /**
   * Sets the positions of the extra element of each option
   */
  extraInListPosition?: "right" | "left";
  rootProps?: Omit<React.ComponentProps<typeof StyledRoot>, "value" | "onChangeValue">;
  triggerProps?: Omit<React.ComponentProps<typeof StyledTrigger>, "disabled">;
  contentProps?: Omit<React.ComponentProps<typeof StyledContent>, "position">;
  itemProps?: Omit<React.ComponentProps<typeof StyledItem>, "value">;
  /**
   * Should the select be disabled?
   */
  disabled?: boolean;
  /**
   * This callback is triggered when the last element of the list is reached (starts being visible)
   */
  onLastElementReached?: () => void;
}

/**
 * A reusable Select component
 */
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
  // eslint-disable-next-line max-lines-per-function
  (
    {
      ariaLabel,
      label,
      placeholder,
      htmlFor,
      options,
      error,
      info,
      infoEllipsis,
      rightElement,
      defaultValue,
      value,
      onValueChange,
      onOpenChange,
      extraInListPosition = "left",
      rootProps,
      triggerProps,
      contentProps,
      itemProps,
      disabled,
      onLastElementReached,
      css,
      "data-id": dataId,
    },
    ref
  ) => {
    const { handleOnValChange, isFilled, triggerRef, selectedOption, contentRef } = useSelect({
      value,
      onValueChange,
      defaultValue,
      options,
    });

    const [lastElement, setLastElement] = useState<HTMLElement | null>(null);
    useInfiniteScroll(lastElement, onLastElementReached);

    const device = useInputDevice();

    return (
      <StyledRoot
        {...(typeof value === "string" && { value })}
        {...(typeof defaultValue === "string" && { defaultValue })}
        onValueChange={handleOnValChange}
        onOpenChange={onOpenChange}
        {...rootProps}
      >
        <StyledWrapper css={css} {...(disabled && { "data-disabled": true })}>
          <StyledTrigger
            ref={triggerRef}
            aria-label={ariaLabel}
            {...triggerProps}
            disabled={disabled}
            filled={isFilled}
            error={error}
            id={htmlFor}
            data-id={dataId}
          >
            {selectedOption ? (
              <StyledValue ref={ref} placeholder={placeholder} aria-label={selectedOption.label}>
                <StyledExtraContainer>
                  {selectedOption.extra}
                  <Flex data-value-container>{selectedOption.label.replace(/ /g, "\u00A0")}</Flex>
                </StyledExtraContainer>
              </StyledValue>
            ) : (
              <StyledValue ref={ref} placeholder={placeholder} data-value-container />
            )}
            <StyledIconContainer>
              {rightElement}
              <StyledIcon>
                <Icons.ChevronDown color="$currentColor" />
              </StyledIcon>
            </StyledIconContainer>
            {label && (
              <StyledLabel filled={isFilled} error={error} htmlFor={htmlFor}>
                {label}
              </StyledLabel>
            )}
          </StyledTrigger>
          <StyledFieldSet aria-hidden error={error}>
            {label && <StyledLegend filled={isFilled}>{label}</StyledLegend>}
          </StyledFieldSet>

          {info && (
            <StyledExtraInfo variant="body4" error={error} ellipsis={infoEllipsis}>
              {info}
            </StyledExtraInfo>
          )}
          <StyledPortal>
            <StyledContent position="popper" ref={contentRef} {...contentProps}>
              <StyledViewport>
                <ScrollArea type={device === "touch" ? "scroll" : "auto"}>
                  <StyledGroup>
                    {options.map(({ value, label, extra, ...internalItemProp }, key) => {
                      const isLast = key === options.length - 1;

                      return (
                        <StyledItem
                          {...itemProps}
                          {...internalItemProp}
                          value={value}
                          key={value}
                          extraPosition={
                            extraInListPosition === "right" && extra ? "right" : "left"
                          }
                          {...(isLast && { ref: setLastElement })}
                        >
                          <Flex css={{ flexShrink: 0 }}>{extra}</Flex>
                          <StyledItemText>{label}</StyledItemText>
                        </StyledItem>
                      );
                    })}
                  </StyledGroup>
                </ScrollArea>
              </StyledViewport>
            </StyledContent>
          </StyledPortal>
        </StyledWrapper>
      </StyledRoot>
    );
  }
);

Select.displayName = "Select";
