import { forwardRef, memo, useCallback, useMemo, useRef, useState } from 'react';
import type { KeyboardEventHandler, MouseEventHandler } from 'react';

import { SortableContainer as sortableContainer } from 'react-sortable-hoc';

import clsx from 'clsx';
import debounce from 'lodash/debounce';
import get from 'lodash/get';

import { Button } from '../button';
import { Grid } from '../@layout/grid';
import { SearchField } from '../@forms/search-field';
import type { SearchFieldProps } from '../@forms/search-field';
import { isElementOverflowing, useResizeObserverEffect } from '../../utils';

import ListItems from './ListItems';
import {
  filterDropdownMenuItemsRecursive,
  highlightDropdownMenuItem,
  processDropdownMenuItemsRecursive
} from './helpers';
import type { DropdownMenuItem, MenuContentProps, SortableProps } from './types';
import createClasses from './styles';

const spaceKeyStopPropagation: KeyboardEventHandler<HTMLElement> = e =>
  e.code === 'Space' && e.stopPropagation();
const stopPropagation: MouseEventHandler<HTMLElement> = e => e.stopPropagation();

const SortableContainer = sortableContainer(({ children }: SortableProps) => children);

// eslint-disable-next-line
const MenuContent = forwardRef<HTMLDivElement, MenuContentProps>((props, ref) => {
  const {
    search,
    handleTextChange,
    searchDebounceTime = 0,
    searchHighlight = false,
    searchPlaceholder,
    searchBy = ['name'],
    classes,
    sortable,
    handleClick,
    listItems,
    modifyMenuItem,
    InsertBeforeList,
    InsertAfterList,
    multiple,
    deletable,
    handleDelete,
    onSortEnd,
    showSelectedIcon,
    bottomButtonText,
    bottomButtonClickHandler,
    bottomButtonProps,
    showSort = true,
    ariaLabel,
    noOptionsText,
    noResultsText,
    loadingText,
    clearSelectItems,
    variableSizeList,
    variableSizeListProps,
    menuListHeader,
    menuListFooter,
    loading,
    menuListLoader,
    infinityScrollProps
  } = props;
  const styles = createClasses({ search, showSort });

  if (infinityScrollProps && search)
    console.warn(
      `The "infinityScrollProps" and "search" are incompatible. May occur unexpected behaviour`
    );

  if (multiple && showSelectedIcon)
    console.warn(
      `The "multiple" and "showSelectedIcon" props were provided. Ignoring conflicting "showSelectedIcon"`
    );

  const [text, setText] = useState('');
  const handleSearchTextChange: SearchFieldProps['onChange'] = useCallback(
    e => {
      setText(e.target.value);
      if (handleTextChange) {
        typeof searchDebounceTime !== 'undefined' && searchDebounceTime >= 0
          ? debounce(handleTextChange, searchDebounceTime)(e)
          : handleTextChange(e);
      }
    },
    [handleTextChange, searchDebounceTime]
  );

  const handleClear: SearchFieldProps['handleClear'] = useCallback(() => {
    setText('');
    if (clearSelectItems) {
      clearSelectItems?.();
    } else {
      handleTextChange?.('');
    }
  }, [handleTextChange, clearSelectItems]);

  const filteredListItems = useMemo(() => {
    if (!text || !searchBy?.length) {
      return listItems;
    }

    const filteredListItems = filterDropdownMenuItemsRecursive(listItems, item =>
      searchBy.some(field =>
        (field in item ? item[field as keyof DropdownMenuItem] : get(item, field))
          ?.toString()
          .toLowerCase()
          .includes(text.toLowerCase())
      )
    );

    return searchHighlight
      ? processDropdownMenuItemsRecursive(filteredListItems, highlightDropdownMenuItem([text]))
      : filteredListItems;
  }, [listItems, searchBy, searchHighlight, text]);

  const listItemsRef = useRef<HTMLUListElement>(null);

  const [isOverflowing, setIsOverflowing] = useState(false);
  const debouncedOverflowCheck = useMemo(
    () =>
      debounce(() => {
        const listElement = variableSizeList
          ? (document.querySelector('.variable-size-list-wrapper') as HTMLElement)
          : listItemsRef?.current;
        if (listElement) setIsOverflowing(isElementOverflowing(listElement));
      }),
    [variableSizeList]
  );
  useResizeObserverEffect(debouncedOverflowCheck, listItemsRef?.current);

  if (loading) {
    return (
      <Grid className={clsx(styles.menuListLoader, classes?.menuListLoader)}>
        {menuListLoader || 'Loading...'}
      </Grid>
    );
  }

  return (
    <Grid
      ref={ref}
      className={clsx(
        isOverflowing && styles.menuListWithScrollbar,
        styles.menuListWrapper,
        classes?.menuListWrapper
      )}
    >
      {search && (
        <SearchField
          id="textField"
          value={text}
          handleClear={handleClear}
          onChange={handleSearchTextChange}
          onClick={stopPropagation}
          onKeyDown={spaceKeyStopPropagation}
          placeholder={searchPlaceholder}
          inputProps={{
            className: styles.textFieldInput
          }}
        />
      )}

      {menuListHeader && (
        <div
          className={clsx(styles.menuListHeader, classes?.menuListHeader)}
          onClick={stopPropagation}
        >
          {menuListHeader}
        </div>
      )}

      {sortable ? (
        <div className={classes?.menuListItems} onClick={stopPropagation}>
          <SortableContainer
            getContainer={() =>
              ((variableSizeList && document.querySelector('.variable-size-list-wrapper')) ||
                document.documentElement) as HTMLElement
            }
            useDragHandle
            onSortEnd={onSortEnd}
          >
            <ListItems
              ref={listItemsRef}
              styles={styles}
              classes={classes}
              handleClick={handleClick}
              listItems={filteredListItems}
              modifyMenuItem={modifyMenuItem}
              InsertBeforeList={InsertBeforeList}
              InsertAfterList={InsertAfterList}
              multiple={multiple}
              sortable={sortable}
              deletable={deletable}
              handleDelete={handleDelete}
              showSelectedIcon={showSelectedIcon}
              ariaLabel={ariaLabel}
              noOptionsText={noOptionsText}
              noResultsText={noResultsText}
              loadingText={loadingText}
              variableSizeList={variableSizeList}
              variableSizeListProps={variableSizeListProps}
              infinityScrollProps={infinityScrollProps}
            />
          </SortableContainer>
        </div>
      ) : (
        <div onClick={stopPropagation}>
          <ListItems
            ref={listItemsRef}
            styles={styles}
            classes={classes}
            handleClick={handleClick}
            listItems={filteredListItems}
            modifyMenuItem={modifyMenuItem}
            InsertBeforeList={InsertBeforeList}
            InsertAfterList={InsertAfterList}
            multiple={multiple}
            sortable={sortable}
            deletable={deletable}
            handleDelete={handleDelete}
            showSelectedIcon={showSelectedIcon}
            ariaLabel={ariaLabel}
            noOptionsText={noOptionsText}
            noResultsText={noResultsText}
            loadingText={loadingText}
            variableSizeList={variableSizeList}
            variableSizeListProps={variableSizeListProps}
            infinityScrollProps={infinityScrollProps}
          />
        </div>
      )}

      {menuListFooter && (
        <div
          className={clsx(styles.menuListFooter, classes?.menuListFooter)}
          onClick={stopPropagation}
        >
          {menuListFooter}
        </div>
      )}

      {(bottomButtonText || bottomButtonProps) && (
        <Button
          color="primary"
          variant="text"
          className={clsx(['buttonBottom', styles.buttonBottom])}
          onClick={bottomButtonClickHandler}
          {...bottomButtonProps}
        >
          {bottomButtonText}
        </Button>
      )}
    </Grid>
  );
});

export default memo(MenuContent);
