import { UseComboboxReturnValue } from 'downshift';
import React, { useRef, useCallback, useEffect, CSSProperties } from 'react';

import { GroupedOption, BaseOption } from '../types';
import { MenuWrapper, CategoryWrapper } from '../StyledSelect';
import { SelectProps } from '.';
import { checkAtBottom, getAccumulatedIndex, getItemOffsetTop } from '../utils';
import { fromNullable, map, getOrElse } from '../../../fp/Option';
import { pipe, noop } from '../../../fp/function';
import Divider from '../../Divider';
import Menu from '../../Menu';
import Typography from '../../Typography';

type OptionListComponentProps<T extends BaseOption> = {
  categories: GroupedOption<T>[];
  hasResults: boolean;
  newOption: BaseOption;
  selectedItem: T | undefined;
  style: CSSProperties;
} & Pick<
  UseComboboxReturnValue<BaseOption>,
  'getMenuProps' | 'getItemProps' | 'highlightedIndex'
> &
  Pick<
    SelectProps<T>,
    | 'optionRenderer'
    | 'loading'
    | 'onScrollListToBottom'
    | 'onCreateNewOption'
    | 'noResults'
  >;

const OptionList = <T extends BaseOption>({
  categories,
  hasResults,
  newOption,
  selectedItem,
  getItemProps,
  getMenuProps,
  highlightedIndex,
  onScrollListToBottom,
  onCreateNewOption,
  loading,
  noResults,
  optionRenderer,
  style,
}: OptionListComponentProps<T>) => {
  const menuRef = useRef<HTMLDivElement | null>(null);
  const activeItemRef = useRef<HTMLLIElement | null>(null);

  const onScrollToBottom = useCallback((): void => {
    const isAtBottom = checkAtBottom(menuRef.current);
    if (
      isAtBottom === true &&
      loading !== true &&
      onScrollListToBottom !== undefined
    ) {
      onScrollListToBottom();
    }
  }, [loading, onScrollListToBottom]);

  useEffect(() => {
    pipe(
      fromNullable(menuRef.current),
      map(el => {
        const offsetTop = getItemOffsetTop(activeItemRef.current);
        if (offsetTop !== undefined) {
          const menuEl = el;
          menuEl.scrollTop = offsetTop;
        }
      }),
      getOrElse(noop)
    );
  }, [selectedItem]);

  return (
    <MenuWrapper
      {...getMenuProps({ ref: menuRef })}
      onScroll={onScrollToBottom}
      style={style}
    >
      <Menu>
        {categories.map(({ category, options }, catIndex) => {
          const accumulatedIndex = getAccumulatedIndex(categories, catIndex);
          return (
            <React.Fragment key={category}>
              {category !== '' && (
                <CategoryWrapper>
                  <Typography.Text
                    tagName="span"
                    fontSize={12}
                    fontWeight="semi-bold"
                    intent="subdued"
                  >
                    {category}
                  </Typography.Text>
                </CategoryWrapper>
              )}
              {options.map((item, index) => {
                const actualIndex = accumulatedIndex + index;
                const isActiveItem = item.value === selectedItem?.value;
                const ariaProps =
                  item.disabled === true
                    ? {}
                    : {
                        ...getItemProps({
                          item,
                          index: actualIndex,
                          ref:
                            isActiveItem === true ? activeItemRef : undefined,
                          onClick: e => e.stopPropagation(),
                        }),
                      };

                return (
                  <Menu.Item
                    key={item.value}
                    text={
                      optionRenderer !== undefined
                        ? optionRenderer({ option: item, index })
                        : item.text
                    }
                    focused={actualIndex === highlightedIndex}
                    active={isActiveItem}
                    disabled={item.disabled}
                    textElement={
                      item.helpText !== undefined && (
                        <Typography.Text
                          tagName="span"
                          intent={isActiveItem ? 'main' : 'subdued'}
                        >
                          {item.helpText}
                        </Typography.Text>
                      )
                    }
                    {...ariaProps}
                  />
                );
              })}
              {catIndex < categories.length - 1 && <Divider />}
            </React.Fragment>
          );
        })}
      </Menu>
      {hasResults === false &&
        loading !== true &&
        onCreateNewOption !== undefined && (
          <Menu.Item
            icon="add"
            text={newOption.text}
            focused={highlightedIndex === 0}
            {...getItemProps({
              item: newOption,
              index: 0,
              onClick: e => e.stopPropagation(),
            })}
          />
        )}
      {hasResults === false && onCreateNewOption === undefined && noResults}
    </MenuWrapper>
  );
};

export default OptionList;
