import { useCallback, useEffect, useMemo, useState } from 'react';
import { useToggle } from 'react-use';

import MuiBox from '@mui/material/Box';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import type { AutocompleteProps, ChipTypeMap } from '@mui/material';
import type { TextFieldProps } from '@mui/material/TextField';

import { ASSETS_URL } from '../../../consts/common';
import { CustomIcon } from '../../custom-icon';

import Components from './components';
import { StyledAutocompleteLI } from './components/styles';
import { makeOptionsArray, groupBy as defaultGroupBy } from './helpers';
import { StyledAutocomplete } from './styles';
import { BaseOptionProps, GlobalSearchBaseProps } from './types';

export const GlobalSearch = <
  T extends BaseOptionProps,
  Multiple extends boolean | undefined,
  DisableClearable extends boolean | undefined,
  FreeSolo extends boolean | undefined,
  ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']
>(
  props: GlobalSearchBaseProps<T> &
    Omit<
      AutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>,
      'renderInput' | 'options'
    >
) => {
  const {
    disableGrouping = false,
    groupedOptions,
    hideEndAdornment = false,
    open,
    openOnce,
    options,
    optionsBeforeScroll,
    endAdornmentIconSrc,
    onEndAdornmentClick,
    disableGroupTitleCasing,
    minInputLength,
    placeholder,
    ...otherProps
  } = props;

  const [openGroup, setOpenGroup] = useState(
    groupedOptions ? Object.keys(groupedOptions)[0] : null
  );

  if (options && groupedOptions)
    console.warn(
      `"options" and "groupedOptions" props were provided. You should only provide one of them. Using "options"`
    );

  const memorizedOptions = useMemo(() => makeOptionsArray(groupedOptions), [groupedOptions]);

  const handleGroupClick = (group: string) => setOpenGroup(prev => (prev === group ? null : group));

  // control inputText, open and minInputLength
  const defaultValue =
    (typeof otherProps.defaultValue === 'string' && otherProps.defaultValue) || '';
  const hasStringInputValue = !otherProps.multiple && typeof otherProps.value === 'string';
  const [inputText, setInputText] = useState(defaultValue);

  useEffect(() => {
    if (hasStringInputValue) setInputText(otherProps.value as string);
  }, [hasStringInputValue, setInputText, otherProps.value]);

  const [openMultiple, toggleOpenMultiple] = useToggle(false);

  const shouldOpenListbox = useCallback(
    (inputText?: string) =>
      !minInputLength || (inputText ? inputText.length >= minInputLength : false),
    [minInputLength]
  );

  const toggleOpenListbox = useCallback(
    (open?: boolean) => {
      if (typeof open === 'boolean' ? open : !openMultiple) {
        if (!openMultiple) {
          toggleOpenMultiple(true);
        }
      } else {
        if (openMultiple) {
          toggleOpenMultiple(false);
        }
      }
    },
    [openMultiple, toggleOpenMultiple]
  );

  const handleTextChange: TextFieldProps['onChange'] = useCallback(
    event => {
      const newValue: string = (event?.target ? event.target.value : event) || '';
      setInputText(newValue);
      toggleOpenListbox(shouldOpenListbox(newValue));
    },
    [setInputText, shouldOpenListbox, toggleOpenListbox]
  );

  const handleTextFieldClick: TextFieldProps['onClick'] = event => {
    if (openMultiple) toggleOpenListbox(); // toggle if already opened
    else toggleOpenListbox(shouldOpenListbox(inputText));
  };

  const handleTextFieldKeyDown: TextFieldProps['onKeyDown'] = event => {
    if (hasStringInputValue) {
      if (event.key === 'Enter') {
        event.stopPropagation();
        toggleOpenListbox();
      } else {
        toggleOpenListbox(true);
      }
    }
  };

  const onClickAway = () => toggleOpenListbox(false);

  useEffect(() => {
    if (openOnce) toggleOpenMultiple(true);
  }, [toggleOpenMultiple, openOnce]);

  return (
    <ClickAwayListener onClickAway={onClickAway}>
      <StyledAutocomplete
        clearIcon={
          <CustomIcon
            data-testid="clearIcon"
            color="inherit"
            src={`${ASSETS_URL}/icons/icon_closeCircle.svg`}
          />
        }
        getOptionLabel={opt => opt.label}
        groupBy={!disableGrouping ? defaultGroupBy : undefined}
        isOptionEqualToValue={(opt, val) =>
          opt.id && val.id ? opt.id === val.id : opt.label === val.label
        }
        open={typeof open !== 'undefined' && !minInputLength ? open : openMultiple}
        options={options || memorizedOptions || []}
        renderGroup={params => (
          <Components.Group
            key={params.key}
            params={params}
            onChange={event => {
              event.stopPropagation();
              handleGroupClick(params.group);
            }}
            expanded={openGroup === params.group}
            optionsBeforeScroll={optionsBeforeScroll}
            disableTitleCasing={disableGroupTitleCasing}
          />
        )}
        renderInput={params => (
          <Components.Input
            data-testid="globalSearchInput"
            endAdornmentIconSrc={endAdornmentIconSrc}
            hideEndAdornment={hideEndAdornment}
            placeholder={placeholder}
            value={inputText}
            onChange={handleTextChange}
            onClick={handleTextFieldClick}
            onEndAdornmentClick={onEndAdornmentClick ? onEndAdornmentClick : handleTextFieldClick}
            onKeyDown={handleTextFieldKeyDown}
            {...params}
          />
        )}
        renderOption={(liProps, option, state) => {
          return (
            <StyledAutocompleteLI {...liProps} key={option.id}>
              <Components.Option
                componentType={option.componentType}
                option={option}
                state={state}
              />
              {option.Actions && (
                <MuiBox onClick={e => e.stopPropagation()} className="li-actions">
                  {option.Actions}
                </MuiBox>
              )}
            </StyledAutocompleteLI>
          );
        }}
        ListboxProps={{
          className: 'global-search',
          style: !disableGrouping ? { maxHeight: 'unset' } : undefined
        }}
        {...otherProps}
      />
    </ClickAwayListener>
  );
};

export default GlobalSearch;
