// @flow
import React from 'react';
import cn from 'classnames';

import Dropdown from './dropdown';
import getStrings from './strings';
import Icon from '../icons';

import './scss/select.scss';

import type { Option } from './selectItem';

/** Props, передаваемые в Select
  * @property {boolean} isMulti - включает функционал мультиселекта
  * @property {Array<{value: any, label: string}>} options - опции для выбора.
  * @property {Function} onChange - коллбэк принимающий массив cо значениями выбранных элементов.
  * @property {Array<string>} selected - значения выбранные по-умолчанию.
  * @property {boolean} hasSelectAll - отображать или нет пункт "Выбрать все".
  * @property {boolean} disableSearch - отображать ли строку поиска.
  * @property {boolean} shouldToggleOnHover - если true то список будет раскрываться
  * при наведении на него мышкой.
  * @property {{[string]: string}} overrideStrings - можно указать свои плейсхолдеры.
  * @property {number} maxHeight - высота списка, при превышении которой появится скролл
  */
type SelectProps = {
    isMulti: boolean,
    options: Array<Option>,
    onChange: Function,
    selected: Array<any>,
    displaySearch?: boolean,
    displayCheckboxes: Boolean,
    displayAllSelected: Boolean,
    shouldToggleOnHover: boolean,
    hasSelectAll: boolean,
    overrideStrings?: {[string]: string},
    maxHeight: number,
};
/** State компонента MultiSelect
  * @property {Array<any>} selected - выбранные пункты
  */
type SelectState = {
  selected: Array<any>,
  itemsWidthDefined: boolean,
};
/** Props, передаваемые в ItemHeader
  * @property{object} option - название (label) выбранного пункта
  */
type ItemHeaderProps = {
  option: Object,
};
/** ItemHeader отрисовывает выбранный пункт в шапке компонента Dropdown */
const ItemHeader = (props: ItemHeaderProps) => {
  if (props.withCancelButton && props.option.value !== '$extraItem') {
    return (
        <div
          className='omni-select__selected-label'
          data-value={props.option.label}
        >
          <div className='omni-select__selected-label-button'>
           <div
            className='omni-select__selected-label-button-cancel'
            onClick={() => props.onClickCancel(props.option)}
           >
              <Icon name='clear' className='omni-select__icon-clear' />
            </div>
            <div style={{
              /* width: '100%',
              'line-height': '1.17em', */
              position: 'relative',
              display: 'grid',
            }}>
              <div className='omni-select__selected-label-value'>
                {props.option.label}
              </div>
            </div>
          </div>
        </div>
    );
  }

  return (
    <div
      className='omni-select__selected-label'
      data-value={props.option.label}
    >
      <div style={{
        /* width: '100%',
        'line-height': '1.17em', */
        position: 'relative',
        display: 'grid',
      }}>
        <div className='omni-select__selected-label-value'>
          {props.option.label}
        </div>
      </div>
    </div>
  );
};

/** Компонент Select
* @example
* const options = [
*   { value: 'chocolate', label: 'Chocolate' },
*   { value: 'strawberry', label: 'Strawberry' },
*   { value: 'vanilla', label: 'Vanilla' },
* ];
* <Select
*   options={options}
*   isMulti
*   onChange={r => console.log(r)}
* />
*/

const toArray = (value) => (Array.isArray(value) ? value : [value]);

class Select extends React.Component<SelectProps, SelectState> {
  static defaultProps = {
    hasSelectAll: false,
    shouldToggleOnHover: false,
    onChange: () => null,
    searchRef: () => null,
    searchOnChange: () => null,
    maxHeight: 200,
  }

  constructor(props: SelectProps) {
    super(props);
    this.state = {
      selected: props.selected ? toArray(props.selected) : [],
      itemsWidthDefined: false,
    };
  }

  itemsWidth: {[string]: number}

  items: ?HTMLElement

  componentDidMount() {
    if (this.items) {
      const dispNone = [...this.items.querySelectorAll('.omni-select_hidden > .omni-select__selected-label')];

      const itemsWidth = dispNone.reduce((acc, item) => {
        acc[item.dataset.value] = item.offsetWidth;
        return acc;
      }, {});
      if (itemsWidth) {
        this.itemsWidth = itemsWidth;
        this.setState({ itemsWidthDefined: true });
      }
    }
  }

  componentDidUpdate(prevProps, prevState) {
    if (JSON.stringify(prevState.selected) !== JSON.stringify(this.state.selected)) {
      this.props.onChange(this.state.selected);
    }
    if (prevProps.selected !== this.props.selected) {
      this.setState({ selected: this.props.selected });
    }
  }

  getSelectedItems(): Array<any> {
    const { options, isMulti } = this.props;
    const { selected } = this.state;
    const selectedOptions: Array<Option> = selected
      .reduce((acc, s: string): any => {
        const option = options.find((o: any): any => o.value === s);
        return option ? [...acc, option] : acc;
      }, []);
    const getLimitIndex = (limitValue, items: Array<Option>) => {
      const result = items.reduce((acc, item, index) => {
        const value = this.itemsWidth[item.label] + acc.value;
        if (index === 0 || value < limitValue) {
          return { value, index: acc.index + 1 };
        }
        return acc;
      }, { value: 0, index: 0 });
      return result;
    };
    if (this.items) {
      const header = this.items.querySelector('.omni-select__dropdown-heading-value');
      const headerWidth = header ? header.clientWidth : 600;
      const limitIndex = getLimitIndex(headerWidth - 50, selectedOptions);

      if (selectedOptions.length > limitIndex.index) {
        // const cropped = selectedOptions.filter((el, index) => index < limitIndex.index - 1);
        const cropped = selectedOptions.slice(0, limitIndex.index);
        const countRestItems = selectedOptions.length - cropped.length;
        const text = `+${countRestItems}`;
        const newSelectedLabels = [...cropped, { label: text, value: '$extraItem' }];
        return newSelectedLabels.map((s, index) => (
          <ItemHeader
            option={s}
            key={`${index}_${s.label}`}
            value={s.value}
            withCancelButton={isMulti}
            onClickCancel = {this.handleClickCancel}
          />
        ));
      }
    }

    return selectedOptions.map(s => (s
      ? <ItemHeader
          option={s}
          key={s.value}
          value={s.value}
          withCancelButton={isMulti}
          onClickCancel = {this.handleClickCancel}
        />
      : ''));
  }

  renderHeader() {
    const { options, overrideStrings, displayAllSelected } = this.props;

    const { selected } = this.state;
    const selectedOptions = selected
      .reduce((acc, s) => {
        const option = options.find((o) => o.value === s);
        return option ? [...acc, option] : acc;
      }, []);
    const optionNotFound = selectedOptions.length === 0;
    const noneSelected = selected.length === 0 || optionNotFound;
    const allSelected = selected.length === options.length;

    if (noneSelected) {
      return (
        <span className='omni-select__dropdown-heading-placeholder'>
          {getStrings('selectSomeItems', overrideStrings)}
        </span>
      );
    }

    return (
      <>
        {allSelected && displayAllSelected
          ? (
            <span className='omni-select__dropdown-heading-placeholder'>
              {getStrings('allItemsAreSelected', overrideStrings)}
            </span>
          )
          : this.getSelectedItems()
        }
      </>
    );
  }

  handleSelectedChanged = (selected: Array<any>) => {
    this.setState({ selected });
  }

  handleClickCancel = (option) => {
    const { selected } = this.state;
    const filtered = selected.filter(v => v !== option.value);
    this.handleSelectedChanged(filtered);
  }

  render() {
    const { selected, itemsWidthDefined } = this.state;
    const {
      state,
      options,
      displaySearch,
      displayCheckboxes,
      shouldToggleOnHover,
      hasSelectAll,
      overrideStrings,
      isMulti,
      maxHeight,
      tip,
      searchRef,
      keyboardRef,
      searchText,
      searchOnChange,
    } = this.props;
    const tipClasses = cn({
      'omni-select__tip': true,
      [`omni-select_${state}`]: !!state,
    });
    const tipItem = tip && typeof tip === 'string'
      ? <span className={tipClasses}>{tip}</span>
      : tip;
    return (
      // eslint-disable-next-line no-return-assign
      <>
        <div ref={ref => this.items = ref} className='omni-ui-general omni-select'>
          <div style={{ overflow: 'hidden', position: 'relative' }}>
            <div className='omni-select_hidden'>
              {options.map((item, index) => (
              <ItemHeader
                option={item}
                key={`${index}_${item.label}`}
                value={item.value}
                withCancelButton={isMulti}
              />))}
            </div>
          </div>
          <Dropdown
            shouldToggleOnHover={shouldToggleOnHover}
            contentProps={{
              state,
              options,
              searchRef,
              selected,
              hasSelectAll,
              onSelectedChanged: this.handleSelectedChanged,
              displaySearch,
              displayCheckboxes,
              overrideStrings,
              isMulti,
              maxHeight,
              keyboardRef,
              searchText,
              searchOnChange,
            }}
          >
              {itemsWidthDefined && this.renderHeader()}
          </Dropdown>
        </div>
        {tip && tipItem}
      </>
    );
  }
}

export default Select;
