import React, { Fragment, useEffect, useId, useRef, useState } from 'react';
import styled from 'styled-components';

import type { JSX } from 'react';
import type { SelectOption, SelectProps } from '@redocly/theme/core/types';

import { CheckmarkIcon } from '@redocly/theme/icons/CheckmarkIcon/CheckmarkIcon';
import { SelectInput } from '@redocly/theme/components/Select/SelectInput';
import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
import { useOutsideClick, useThemeHooks } from '@redocly/theme/core/hooks';

export function Select<T>(props: SelectProps<T>): JSX.Element {
  const {
    className,
    value,
    options,
    dataAttributes,
    withArrow = true,
    triggerEvent = 'click',
    placement,
    alignment,
    icon,
    onlyIcon,
    disabled,
    placeholder,
    hideCheckmarkIcon,
    checkmarkIconPosition,
    dataTestId = 'select',
    multiple,
    searchable,
    clearable,
    footer,
    onChange,
    onSearch,
    renderInput,
    renderDivider,
  } = props;
  const { useTranslate } = useThemeHooks();
  const { translate } = useTranslate();

  const getSelectedOptionsFromPropsValue = () => {
    const values = Array.isArray(value) ? value : [value];
    return values
      .map((value) => {
        const selectedOption = options.find((option) => option.value === value);
        return (
          selectedOption ||
          (typeof value === 'string' ? ({ value } as SelectOption<T>) : (value as SelectOption<T>))
        );
      })
      .filter((option) => !!option);
  };

  const [selectedOptions, setSelectedOptions] = useState<SelectOption<T>[]>(
    getSelectedOptionsFromPropsValue(),
  );
  const selectRef = useRef<HTMLDivElement | null>(null);
  const selectInputRef = useRef<HTMLInputElement | null>(null);
  const [searchValue, setSearchValue] = useState<string | null>(null);
  const [dropdownActive, setDropdownActive] = useState<boolean | undefined>(false);
  const [filteredOptions, setFilteredOptions] = useState<SelectOption<T>[]>(options);
  const [stickyInputValue, setStickyInputValue] = useState<string>(placeholder || '');
  const inputId = useId();

  useOutsideClick(selectRef, () => {
    setDropdownActive(false);
  });

  useEffect(() => {
    setFilteredOptions(options);
  }, [options]);

  useEffect(() => {
    setSelectedOptions(getSelectedOptionsFromPropsValue());
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [multiple, value]);

  useEffect(() => {
    if (onSearch) {
      onSearch?.(searchValue as T);
    } else {
      if (typeof searchValue === 'string') {
        const filteredOptions = options.filter((option) => {
          const valueForSearch = String(option.label ?? option.value ?? '');
          return (
            !valueForSearch || valueForSearch.toLowerCase().indexOf(searchValue.toLowerCase()) > -1
          );
        });
        setFilteredOptions(filteredOptions);
      } else {
        setFilteredOptions(options);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [searchValue]);

  const selectHandler = (selectedOption: SelectOption<T>) => {
    const newSelectedOptions = multiple
      ? isSelected(selectedOption)
        ? selectedOptions.filter((option) => option.value !== selectedOption.value)
        : [...selectedOptions, selectedOption]
      : [selectedOption];

    const newSelectedValues = newSelectedOptions.length
      ? multiple
        ? newSelectedOptions.map((o) => o.value)
        : newSelectedOptions[0].value
      : multiple
        ? []
        : ('' as T);

    setSelectedOptions(newSelectedOptions);
    onChange?.(newSelectedValues);
    setSearchValue(null);
    setDropdownActive(false);

    if (!multiple) {
      setStickyInputValue('');
    }

    selectInputRef.current?.focus();
  };

  const searchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    const targetValue = e.target.value;
    setSearchValue(targetValue);
    setDropdownActive(true);
  };

  const clearHandler = (option?: SelectOption<T>) => {
    if (option) {
      selectHandler(option);
    } else {
      if (!multiple) {
        setStickyInputValue('');
      }
      setSelectedOptions([]);
      onChange?.(multiple ? [] : ('' as T));
    }
  };

  const inputBlurHandler = (e: React.FocusEvent) => {
    const relatedTarget = e.relatedTarget as HTMLElement;
    const isDropdownItem =
      relatedTarget?.attributes.getNamedItem('data-component-name')?.value ===
      'Dropdown/DropdownMenuItem';
    if (!isDropdownItem) {
      if (!e.relatedTarget || e.relatedTarget?.id !== inputId) {
        setDropdownActive(false);
      }
      setSearchValue(null);
      setStickyInputValue('');
    }
  };

  const inputFocusHandler = () => {
    if (!multiple && selectedOptions.length) {
      setStickyInputValue(selectedOptions[0].label || (selectedOptions[0].value as string));
    }
  };

  const inputClickHandler = () => {
    setDropdownActive(!dropdownActive);
  };

  const isSelected = (option: SelectOption<T>) => {
    return !!selectedOptions.find(
      (selectOption) => selectOption.value === option.value || selectOption.value === option.label,
    );
  };

  const renderDefaultInput = (inputRef: React.ForwardedRef<HTMLInputElement>) => {
    return (
      <SelectInput
        id={inputId}
        listboxId={inputId}
        selectedOptions={selectedOptions}
        searchValue={searchValue}
        placeholder={placeholder}
        stickyValue={stickyInputValue}
        multiple={multiple}
        searchable={searchable}
        clearable={clearable}
        customIcon={icon}
        inputRef={inputRef}
        onlyIcon={onlyIcon}
        clearHandler={clearHandler}
        searchHandler={searchHandler}
        inputBlurHandler={inputBlurHandler}
        inputFocusHandler={inputFocusHandler}
        clickHandler={inputClickHandler}
      />
    );
  };

  return (
    <SelectWrapper
      data-component-name="Select/Select"
      ref={selectRef}
      {...dataAttributes}
      disabled={disabled}
      data-testid={dataTestId}
      className={className}
      onKeyDown={(e) => {
        if (e.key === 'Enter' && document.activeElement === selectRef.current) {
          e.preventDefault();
          setDropdownActive(!dropdownActive);
        }
      }}
      tabIndex={0}
      aria-haspopup="listbox"
      aria-expanded={dropdownActive}
      aria-label="Select option"
      onFocus={(e) => {
        if (e.target === selectRef.current) {
          selectInputRef.current?.focus();
        }
      }}
      onMouseDown={(e) => {
        e.preventDefault();
      }}
    >
      <SelectDropdown
        closeOnClick={!multiple}
        withArrow={withArrow}
        trigger={renderInput ? renderInput() : renderDefaultInput(selectInputRef)}
        triggerEvent={triggerEvent}
        placement={placement}
        alignment={alignment}
        active={!renderInput ? dropdownActive : undefined}
      >
        <SelectDropdownMenu role="listbox" footer={footer}>
          {filteredOptions.length === 0 ? (
            <DropdownMenuItem disabled>
              {translate('select.noResults', 'No results')}
            </DropdownMenuItem>
          ) : (
            filteredOptions.map((option, index) => {
              return (
                <Fragment key={index}>
                  <DropdownMenuItem
                    onAction={() => selectHandler(option)}
                    prefix={
                      !hideCheckmarkIcon &&
                      (checkmarkIconPosition ? checkmarkIconPosition === 'start' : false) &&
                      isSelected(option) && <CheckmarkIcon />
                    }
                    suffix={
                      !hideCheckmarkIcon &&
                      (checkmarkIconPosition ? checkmarkIconPosition === 'end' : true) &&
                      isSelected(option) && <CheckmarkIcon />
                    }
                  >
                    {typeof option.element === 'string' ? (
                      <div>{option.element}</div>
                    ) : (
                      option.element
                    )}
                  </DropdownMenuItem>
                  {renderDivider && index !== options.length - 1 ? renderDivider() : null}
                </Fragment>
              );
            }) || translate('select.noResults', 'No results')
          )}
        </SelectDropdownMenu>
      </SelectDropdown>
    </SelectWrapper>
  );
}

export const SelectWrapper = styled.div<{ disabled?: boolean }>`
  display: flex;
  position: relative;
  font-size: var(--select-font-size);
  font-weight: var(--select-font-weight);
  line-height: var(--select-line-height);
  border-radius: var(--select-border-radius);
  border: var(--select-border);
  color: var(--select-text-color);
  min-width: 0;

  &:focus-visible {
    outline: 1px solid var(--button-border-color-focused);
  }

  ${({ disabled }) =>
    disabled &&
    `
    opacity: 0.59;
    pointer-events: none;
  `}
`;

const SelectDropdown = styled(Dropdown)`
  width: 100%;

  > * {
    min-width: 100%;
  }
`;

const SelectDropdownMenu = styled(DropdownMenu)`
  width: 100%;
  margin-top: var(--spacing-unit);
`;
