import * as React from 'react';
import cn from 'classnames';
import { useEventListener } from 'hooks';
import { Space, Label } from 'components/atoms';
import { Loader } from 'components/molecules';
import { Item, ItemProps } from './Item';

import { Context } from '../../Context';
import { SelectMode, OptionValue, Option } from '../../interfaces';
import { ScrollMode } from '../Pagination';

export interface OptionsComposition {
  Item: React.FC<ItemProps>;
}

export interface OptionsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
  mode?: SelectMode;
  scrollMode?: ScrollMode;
  options?: Option[];
  loading?: boolean;
  value?: OptionValue | OptionValue[];
  emptyLabel?: string;
  newOption?: string;
  onPrev?: () => void;
  onNext?: () => void;
  onClose?: () => void;
  onChange?: (value: OptionValue | OptionValue[]) => void;
  onClickAddNew?: (value: string) => void;
}

const Options: React.FC<OptionsProps> = ({
  mode = 'single',
  scrollMode = 'regular',
  loading,
  options = [],
  value,
  emptyLabel = 'No found',
  newOption,
  onNext,
  onClose,
  onChange,
  onClickAddNew,
  ...props
}) => {
  const ref = React.useRef<HTMLDivElement | null>(null);
  const { offsetBottom, maxHeight, setState } = React.useContext(Context);
  const [activeItem, setActiveItem] = React.useState(0);

  const onScroll = (e): void => {
    const scrollTop = Math.floor(e.target.scrollTop);

    if (scrollMode !== 'regular' && onNext && e.target.scrollHeight - e.target.offsetHeight - scrollTop <= 3) {
      onNext();
    }
  };

  React.useEffect(() => {
    const rect = ref.current?.getBoundingClientRect();

    if (!maxHeight && rect?.height && offsetBottom && options.length && !loading) {
      const height = window.innerHeight - offsetBottom;

      setState({ maxHeight: height <= rect.height ? height : rect.height - +(scrollMode === 'scroll') });
    }
  }, [ref.current, offsetBottom, scrollMode, options, loading, maxHeight]);

  React.useEffect(() => {
    if (ref.current && !scrollMode && options?.length) {
      ref.current.scrollTop = 0;
    }
  }, [ref.current, options, scrollMode]);

  React.useEffect(() => {
    if (ref.current && scrollMode === 'scroll') {
      ref.current.addEventListener('scroll', onScroll);
    }

    return () => {
      if (ref.current && scrollMode === 'scroll') {
        ref.current.removeEventListener('scroll', onScroll);
      }
    };
  }, [ref.current, onScroll, scrollMode]);

  useEventListener(
    'keydown',
    ({ key }: { key: string }) => {
      if (['ArrowUp', 'ArrowDown', 'Escape', 'Enter'].includes(key)) {
        if (key === 'Escape' && mode === 'single' && onClose !== undefined) {
          onClose();
        }

        if (key === 'Enter' && onChange !== undefined) {
          const option = options[activeItem - 1];
          onChange(option.value);

          if (mode === 'single' && onClose !== undefined) {
            onClose();
          }
        }

        // User pressed the up arrow
        if (key === 'ArrowUp') {
          setActiveItem((s) => (s !== 0 ? s - 1 : options.length));
        }

        // User pressed the down arrow
        if (key === 'ArrowDown') {
          setActiveItem((s) => (s < options.length ? s + 1 : 0));
        }
      }
    },
    ref,
  );

  const onChangeHandler = (value: any): void => {
    if (onChange !== undefined) {
      onChange(value);
    }

    if (onClose !== undefined) {
      onClose();
    }
  };

  return (
    <div
      ref={ref}
      {...props}
      className={cn(
        'ebs-select__options-items',
        {
          'ebs-select__options--multiple': ['multiple', 'tags'].includes(mode),
        },
        props.className,
      )}
      style={maxHeight ? { ...props.style, maxHeight } : props.style}
    >
      {newOption && onClickAddNew && (
        <Item
          value={newOption}
          text={newOption}
          selected
          onClick={() => onClickAddNew(newOption)}
          suffix={<Label type="ghost" text="CTRL+Enter" />}
        />
      )}

      <Loader
        loading={scrollMode !== 'scroll' ? loading || false : false}
        height={!ref.current || ref.current?.offsetHeight < 300 ? 300 : ref.current.offsetHeight}
      >
        {options.length
          ? options.map((option, key) => (
              <Item
                key={key}
                active={
                  ['multiple', 'tags'].includes(mode) && Array.isArray(value)
                    ? value.includes(option.value)
                    : value === option.value
                }
                mode={mode}
                text={option.text}
                selected={activeItem === key + 1}
                onClick={onChangeHandler}
                {...option}
              />
            ))
          : !loading && (
              <Space size="large" justify="center" className="ebs-select__options--empty">
                {emptyLabel}
              </Space>
            )}
      </Loader>

      {loading ? (
        <Space justify="center" className="mt-10">
          <Loader.Inline />
        </Space>
      ) : null}
    </div>
  );
};

const OptionsComponent: React.FC<OptionsProps> & OptionsComposition = ({ ...props }) => {
  return <Options {...props} />;
};

OptionsComponent.displayName = 'Options';

OptionsComponent.Item = Item;

export { Options, OptionsComponent };