import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Multiselect } from '../multiselect/multiselect';
import { BEM_CLASS, SFiltersSelect } from './s-filters-select';
import { bem } from 'utils/bem';
import { components as selectComponents, GroupBase, ValueContainerProps } from 'react-select';
import { callbackWithoutPropagation } from 'utils/event';
import { SearchContainer } from './components/search-container';
import { MenuPortalProps, MenuProps } from 'react-select/dist/declarations/src/components/Menu';
import { ControlProps } from 'react-select/dist/declarations/src/components/Control';
import { BaseOption, Select, SSelectStyledDiv } from '../select';
import { FilterSelectProps, isMultiProps } from './types';
import { EmptyState } from './components/empty-state';
import { getNearestScrollableParentElement } from 'utils/dom';
import { LongTableTitle } from 'common/components/table';
import { debounce } from 'lodash';
import { SEARCH_DEBOUNCE_TIME } from 'common/constants';

const classes = bem(BEM_CLASS);

export const FiltersSelect = <T extends BaseOption>(props: FilterSelectProps<T>) => {
  const {
    searchPlaceholder,
    bgStyle,
    onChange,
    components,
    isSearchable,
    dataSelector,
    renderMenuFooter,
    isMulti,
    containerClassName,
  } = props;
  const [menuIsOpen, setMenuOpen] = useState(props.menuIsOpen || false);
  const [searchValue, setSearchValue] = useState<string>('');
  const [isControlFocused, setControlFocused] = useState<boolean>(false);

  const triggerRef = useRef<HTMLDivElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);

  const debouncedSearch = useCallback(
    debounce((value: string) => setSearchValue(value), SEARCH_DEBOUNCE_TIME),
    [setSearchValue]
  );

  const onSearchChange = useCallback((value: string) => debouncedSearch(value), [debouncedSearch]);

  const clearSearchInput = useCallback(() => setSearchValue(''), []);

  const closeModal = useCallback(
    event => {
      if (isControlFocused) {
        setControlFocused(false);
      }

      if (!menuRef?.current?.contains(event.target as Node)) {
        setMenuOpen(false);
      }
    },
    [isControlFocused]
  );

  useEffect(() => {
    document.addEventListener('click', closeModal, { capture: true });

    return () => {
      document.removeEventListener('click', closeModal, { capture: true });
    };
  }, [closeModal]);

  useEffect(() => {
    const nearestSrollableContainer = getNearestScrollableParentElement(triggerRef.current);

    nearestSrollableContainer?.addEventListener('scroll', closeModal);

    return () => {
      nearestSrollableContainer?.removeEventListener('scroll', closeModal);
    };
  }, [closeModal, triggerRef]);

  const triggerMenuVisibility = useCallback(() => {
    setControlFocused(true);
    setMenuOpen(!menuIsOpen);
  }, [menuIsOpen]);

  useEffect(() => {
    setMenuOpen(props.menuIsOpen);
  }, [props.menuIsOpen]);

  const renderMenu = useCallback(
    (props: MenuProps<T>) => {
      const optionsLength = props.selectProps.options?.length || 0;
      return (
        <div ref={menuRef}>
          <selectComponents.Menu {...props} className={classes('menu', undefined, 'react-select__menu')}>
            <>
              {isSearchable && (
                <SearchContainer
                  onSearchQueryChange={onSearchChange}
                  value={props.selectProps.inputValue}
                  onMouseDown={callbackWithoutPropagation((event: SyntheticEvent<HTMLInputElement>) =>
                    event.currentTarget.focus()
                  )}
                  searchPlaceholder={searchPlaceholder}
                />
              )}
              <div className={classes('menu-items')} onClick={callbackWithoutPropagation()}>
                {props.children}
              </div>
              {optionsLength > 0 &&
                renderMenuFooter?.(props, () => {
                  clearSearchInput();
                  setMenuOpen(false);
                })}
            </>
          </selectComponents.Menu>
        </div>
      );
    },
    [clearSearchInput, isSearchable, onSearchChange, renderMenuFooter, searchPlaceholder]
  );

  const renderControl = useCallback(
    (props: ControlProps<T>) =>
      components?.Control ? (
        components.Control({ ...props, innerProps: { onMouseDown: triggerMenuVisibility } })
      ) : (
        <div
          ref={triggerRef}
          onClick={triggerMenuVisibility}
          className="react-select__control-container"
          data-selector={`${dataSelector}-control`}
        >
          <selectComponents.Control
            {...props}
            data-selector={dataSelector}
            className={classes('control', undefined, 'react-select__control')}
          >
            {props.children}
          </selectComponents.Control>
        </div>
      ),
    [triggerMenuVisibility, components, dataSelector, triggerRef]
  );

  const renderValueContainer = useCallback(({ selectProps: { value, placeholder } }: ValueContainerProps<T>) => {
    // @ts-ignore: react-select has a wrong type for single value
    const text = Array.isArray(value) ? value?.[0]?.name : value?.name;
    const containerClassName = text === undefined ? 'react-select__placeholder' : 'react-select__single-value';
    return <LongTableTitle text={text || placeholder} className={containerClassName} />;
  }, []);

  const renderPortal = useCallback(
    ({ children, ...rest }: MenuPortalProps<T, boolean, GroupBase<T>>) => (
      <selectComponents.MenuPortal {...rest}>
        <SFiltersSelect bgStyle={bgStyle} isSearchable={isSearchable}>
          <SSelectStyledDiv>{children}</SSelectStyledDiv>
        </SFiltersSelect>
      </selectComponents.MenuPortal>
    ),
    [bgStyle, isSearchable]
  );

  const renderEmptyState = useCallback(() => <EmptyState />, []);

  const onChangeSingleValue = useCallback(
    value => {
      setMenuOpen(false);
      onChange(value);
    },
    [onChange]
  );

  const filtersComponents = useMemo(
    () => ({
      ...(!isMulti && { ValueContainer: renderValueContainer }),
      Menu: renderMenu,
      NoOptionsMessage: renderEmptyState,
      ...components,
      Control: renderControl,
      MenuPortal: props.components.MenuPortal ?? renderPortal,
    }),
    [
      isMulti,
      renderValueContainer,
      renderMenu,
      renderEmptyState,
      components,
      renderControl,
      props.components.MenuPortal,
      renderPortal,
    ]
  );

  return (
    <SFiltersSelect bgStyle={bgStyle} isSearchable={isSearchable} className={containerClassName}>
      {isMultiProps(props) ? (
        <Multiselect
          {...props}
          openMenuOnClick={false}
          inputValue={searchValue}
          menuIsOpen={menuIsOpen}
          isSearchable={false}
          components={filtersComponents}
          backspaceRemovesValue={false}
          tabSelectsValue={false}
        />
      ) : (
        <Select
          {...props}
          openMenuOnClick={false}
          inputValue={searchValue}
          menuIsOpen={menuIsOpen}
          isSearchable={false}
          components={filtersComponents}
          onChange={onChangeSingleValue}
          backspaceRemovesValue={false}
          tabSelectsValue={false}
        />
      )}
    </SFiltersSelect>
  );
};
