import React, { ReactNode, useCallback, useMemo } from 'react';
import { components as selectComponents, InputAction } from 'react-select';
import { uniq } from 'lodash';
import { BaseOption, MultiSelectProps, OptionIdType, Select } from '../select';
import { TreeItemProps, TreeItemType, TreeList } from '../../tree-list';
import { bem } from 'utils/bem';
import { SMultiSelectTree } from './s-multiselect-tree';
import { MultiSelectItem } from './components/multi-select-item';
import { mapOptionsToNode } from './transducers';
import { Ellipsis } from './ellipsis';
import { LabelItem } from './components';
import { TextValueItem } from './components/text-value-item';

const classes = bem('multi-select');

export interface MultiSelectComponentProps<T extends BaseOption> extends MultiSelectProps<T> {
  visibleValues?: number;
  maxLength?: number;
  tree?: boolean;
  useFlatControl?: boolean;
}

interface MenuListProps<T> {
  setValue(valuesIds: OptionIdType[], action: InputAction);
  selectProps: { value: T[]; inputValue: string; defaultExpandedKeys: string[] };
  options: T[];
  children: ReactNode;
}

export const Multiselect = React.memo(<T extends BaseOption>(props: MultiSelectComponentProps<T>) => {
  const {
    visibleValues = 10,
    value = [],
    tree = false,
    NoOptionsMessage = selectComponents.NoOptionsMessage,
    onChange,
    components,
    disabled,
    maxLength,
    className,
    dataSelector,
    useFlatControl = false,
  } = props;
  const isOverLimit = maxLength && maxLength <= value.length;

  const onValuesChange = useCallback(
    values => {
      if (isOverLimit && values.length > value.length) return;

      onChange(uniq(values));
    },
    [onChange, isOverLimit, value.length]
  );

  const renderTreeItem = useCallback(
    (
      { name, type, depth, onClick, isChecked, isHalfChecked, onExpand, isExpanded, isLeaf }: TreeItemProps,
      isDisabled?: boolean
    ) => {
      if (type === TreeItemType.CHECKBOX) {
        return (
          <MultiSelectItem
            label={name}
            depth={depth}
            onClick={onClick}
            isChecked={isChecked}
            isHalfChecked={isHalfChecked}
            onExpand={onExpand}
            isExpanded={isExpanded}
            isDisabled={isDisabled}
            isLeaf={isLeaf}
            dataSelector={dataSelector}
          />
        );
      }

      return <LabelItem name={name} />;
    },
    [dataSelector]
  );

  const treeList = useCallback(
    (props: MenuListProps<T>) => {
      const {
        setValue,
        selectProps: { value, inputValue, defaultExpandedKeys },
        options,
      } = props;
      const defaultExpandedIds = defaultExpandedKeys.map(key => parseInt(key, 10));

      return (
        <SMultiSelectTree className={classes('tree-container')}>
          <TreeList
            onChange={values => setValue(values, 'set-value')}
            value={value.map(value => (value.id !== undefined ? value.id : -1))}
            nodes={mapOptionsToNode(options)}
            className={classes('tree')}
            searchQuery={inputValue}
            renderTreeItem={props =>
              renderTreeItem({ ...props, isLeaf: props.isLeaf || !!inputValue }, props.isDisabled)
            }
            defaultExpandedIds={defaultExpandedIds}
            renderEmptyPlaceholder={() => <div>{props.children}</div>}
            isExpandable
          />
        </SMultiSelectTree>
      );
    },
    [renderTreeItem]
  );

  const renderValueContainer = useCallback(
    ({ children, ...rest }) => {
      const value = rest.getValue();
      const childrenArray = React.Children.toArray(children);
      const childrenArrayWithoutInput = childrenArray.slice(0, childrenArray.length - 1);
      const maxVisibleValue = rest.selectProps.visibleValues;
      const visibleValuesNames = value
        .slice(0, maxVisibleValue)
        .map(option => option.name)
        .join(', ');

      return (
        <selectComponents.ValueContainer {...rest} data-selector={`${dataSelector}-value-container`}>
          {useFlatControl ? (
            <TextValueItem value={visibleValuesNames} />
          ) : (
            childrenArrayWithoutInput.slice(0, maxVisibleValue)
          )}
          {value && Array.isArray(value) && value.length > maxVisibleValue && (
            <>
              <Ellipsis
                remainingItems={value.slice(maxVisibleValue, value.length)}
                disabled={rest.selectProps.isDisabled}
              />
            </>
          )}
          {childrenArray[childrenArray.length - 1]}
        </selectComponents.ValueContainer>
      );
    },
    [dataSelector, useFlatControl]
  );

  const newComponents = useMemo(() => {
    return {
      ValueContainer: renderValueContainer,
      Option: ({ data, isDisabled, isSelected, halfChecked, innerProps }) => {
        return renderTreeItem(
          {
            name: data.name,
            depth: 1,
            onClick: innerProps.onClick,
            isChecked: isSelected,
            isHalfChecked: halfChecked,
            isLeaf: true,
            type: TreeItemType.CHECKBOX,
          },
          isDisabled
        );
      },
      MenuList: tree ? treeList : selectComponents.MenuList,
      NoOptionsMessage,
      ...components,
    };
  }, [NoOptionsMessage, components, treeList, tree, renderTreeItem, renderValueContainer]);

  return (
    <Select
      className={className}
      {...props}
      isMulti
      onChange={onValuesChange}
      isClearable={false}
      closeMenuOnSelect={false}
      hideSelectedOptions={false}
      visibleValues={visibleValues}
      value={value}
      isDisabled={disabled}
      components={newComponents}
      data-selector={dataSelector}
    />
  );
});
