import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import List from 'react-virtualized/dist/commonjs/List';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import CellMeasurerCache from 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache'
import CellMeasurer from 'react-virtualized/dist/commonjs/CellMeasurer'
import { I18n as I18nDecorator } from '@procore/core-react';
import { equals } from 'ramda';

import { FILTERS_ANALYTICS } from './Filters';
import {
  checkboxIcon,
  checkboxCheckedIcon,
  checkboxIndeterminateIcon,
  clearIcon,
  searchIcon
} from './FilterIcons';

function buildSelectedKeys(selected) {
  return selected.reduce((acc, v) => ({ ...acc, ...{ [v]: true } }), {});
}

export class FilterCheckbox extends React.Component {
  static propTypes = {
    filter: PropTypes.shape({
      endpoint: PropTypes.string.isRequired,
      key: PropTypes.string.isRequired,
      value: PropTypes.string.isRequired
    }).isRequired,
    filterCheckboxRowHeight: PropTypes.number.isRequired,
    focusedDropdown: PropTypes.shape(),
    focusedDropdownFilters: PropTypes.arrayOf(PropTypes.shape()),
    I18n: PropTypes.shape().isRequired,
    onAnalytics: PropTypes.func.isRequired,
    onFetch: PropTypes.func.isRequired,
    onRemove: PropTypes.func.isRequired,
    onSave: PropTypes.func.isRequired,
    options: PropTypes.shape(),
    selected: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
    styles: PropTypes.shape()
  }

  static defaultProps = {
    focusedDropdown: null,
    focusedDropdownFilters: null,
    selected: [],
    styles: {},
    options: {}
  };

  constructor(props) {
    super(props);
    const { selected } = this.props;
    const selectedKeys = buildSelectedKeys(selected);

    this.ignoreBlur = false;

    this.cache = new CellMeasurerCache({
      defaultHeight: 24,
      fixedWidth: true
    });

    this.state = {
      containerHeight: props.filterCheckboxRowHeight,
      data: [],
      expanded: false,
      focusedItem: 0,
      hasLoaded: false,
      filteredKeys: [],
      isLoading: false,
      isAllChecked: false,
      scrollToIndex: 0,
      selectedKeys,
      value: ''
    };
  }

  componentDidMount() {
    // If there's no selections on instantiation, it's a new filter. Expand and load. Ben 170818
    if (this.props.selected.length === 0) {
      this.onContainerClick({});
    }

    window.addEventListener('click', this.onBlur);
  }

  componentWillReceiveProps(nextProps) {
    const {
      selected,
      focusedDropdown,
      focusedDropdownFilters,
      filter
    } = nextProps;
    const newSelectedKeys = buildSelectedKeys(selected);
    const { selectedKeys } = this.state;

    if (focusedDropdown && focusedDropdownFilters) {
      const isFocused = focusedDropdown.key === filter.key;

      if (isFocused && focusedDropdownFilters !== this.props.focusedDropdownFilters) {
        this.onComplete(focusedDropdownFilters);
      }
    }

    if (!this.state.dirty && !equals(selectedKeys, newSelectedKeys)) {
      this.state.selectedKeys = newSelectedKeys;
    }
  }

  componentWillUnmount() {
    window.removeEventListener('click', this.onBlur);
  }

  onComplete = (data) => {
    this.setState({
      data: data || [],
      expanded: true,
      filteredKeys: data || [],
      isLoading: false,
      hasLoaded: true
    }, () => { this.searchInput && this.searchInput.focus(); });
  }

  onBlur = () => {
    if (this.ignoreBlur === true) {
      this.ignoreBlur = false;
      return;
    }
    this.setState({ expanded: false, isLoading: false, dirty: false }, () => {
      const { selectedKeys } = this.state;
      const keys = Object.keys(selectedKeys)
        .reduce((acc, k) => (selectedKeys[k] ? acc.concat(k) : acc), []);

      this.props.onSave(this.props.filter, keys, this.props.options);
    });
  }

  onContainerClick = (evt) => {
    if (this.props.disabled) {
      return;
    }

    this.ignoreBlur = true;

    setTimeout(() => {
      this.ignoreBlur = false;
    }, 100);

    if (evt.currentTarget === this.container && this.state.expanded === true) {
      this.ignoreBlur = false;
      this.onBlur(evt);
      return;
    }

    if (this.state.hasLoaded) {
      this.setState({ expanded: true }, () => {
        this.onClearSearch();
      });
    } else {
      this.setState({ isLoading: true }, () => {
        this.props.onFetch(this.props.filter, this.onComplete);
      });
    }
  }

  onCheckOne = (evt) => {
    evt.stopPropagation();

    const { filteredKeys, selectedKeys } = this.state;
    let key;

    if (evt.key === 'Enter') {
      key = this.state.filteredKeys[this.state.focusedItem].key;
    } else {
      key = evt.currentTarget.dataset.key;
    }

    selectedKeys[key] = !selectedKeys[key];

    const isAllChecked = filteredKeys.every(item => selectedKeys[item.key]);

    this.setState({ isAllChecked, selectedKeys, dirty: true });
  }

  onCheckAll = (evt) => {
    evt.stopPropagation();

    const { filteredKeys, isAllChecked } = this.state;
    const selectedKeys = { ...this.state.selectedKeys };

    filteredKeys.forEach((fk) => {
      selectedKeys[fk.key] = !isAllChecked;
    });

    if (isAllChecked) {
      this.setState({ isAllChecked: false, selectedKeys, dirty: true });
      this.props.onAnalytics(FILTERS_ANALYTICS.CLEAR_ALL);
    } else {
      this.setState({ isAllChecked: true, selectedKeys, dirty: true });
      this.props.onAnalytics(FILTERS_ANALYTICS.SELECT_ALL);
    }
  }

  onSearch = (evt) => {
    this.cache.clearAll()
    const { selectedKeys } = this.state;

    const value = evt.target.value.toLowerCase();

    const filteredKeys = this.state.data.filter(item =>
      item.value.toLowerCase().includes(value.toLowerCase() || ''));

    const isAllChecked = filteredKeys.every(item => selectedKeys[item.key]);

    this.setState({
      filteredKeys,
      focusedItem: 0,
      isAllChecked,
      value
    });
  }

  onClearSearch = () => {
    this.cache.clearAll()
    if (this.searchInput) {
      this.searchInput.focus();
      this.searchInput.value = '';
    }

    this.setState({
      filteredKeys: this.state.data,
      focusedItem: 0,
      scrollToIndex: 0,
      value: ''
    });
  }

  onKeyDown = (evt) => {
    const { filteredKeys } = this.state;
    let { focusedItem } = this.state;

    switch (evt.key) {
      case 'Tab':
      case 'Escape':
        this.onBlur();
        break;
      case 'ArrowDown':
        evt.preventDefault();
        if (focusedItem < filteredKeys.length - 1) {
          focusedItem += 1;
          this.scrollDown(focusedItem);
          this.setState({ focusedItem });
        }
        break;
      case 'ArrowUp':
        evt.preventDefault();
        if (focusedItem > 0) {
          focusedItem -= 1;
          this.scrollUp(focusedItem);
          this.setState({ focusedItem });
        }
        break;
      case 'Enter':
        this.onCheckOne(evt);
        break;
      default:
        break;
    }
  };

  onHoverItem = item => this.setState({ focusedItem: item });

  scrollDown = (focusedItem) => {
    this.setState({ scrollToIndex: focusedItem });
  }

  scrollUp = (focusedItem) => {
    this.setState({ scrollToIndex: focusedItem });
  }

  onRowsRendered = () => {
    const containerHeight = Object.values(
      this.cache._rowHeightCache
    ).reduce((total, height = 0) => total + height, 0);

    this.setState({
      containerHeight: Math.max(
        this.props.filterCheckboxRowHeight,
        containerHeight
      )
    });
  }

  render() {
    const {
      filter,
      I18n,
      onRemove,
      styles,
      disabled
    } = this.props;

    const {
      data,
      expanded,
      filteredKeys,
      focusedItem,
      isAllChecked,
      isLoading,
      scrollToIndex,
      selectedKeys
    } = this.state;

    const count = Object.values(selectedKeys).reduce((acc, isChecked) => acc + isChecked, 0);

    const formattedCount = (count > 0
      ? <div className={cx(styles.filterCheckboxCount, { [styles.disabled]: disabled })}>({count})</div>
      : null);

    const removeAllStyle = cx(styles.tokenRemove, {
      [styles.expanded]: expanded,
      [styles.disabled]: disabled
    });

    const remove = (
      <button
        disabled={disabled}
        className={removeAllStyle}
        data-key={filter.key}
        data-qa={`remove_${filter.key}`}
        onClick={onRemove}
      />
    );

    const spinner = isLoading
      ? <div className={styles.tokenLoading} />
      : null;

    const caret = isLoading
      ? null
      : (<div className={cx(styles.tokenCaret, { [styles.disabled]: disabled })}>
        <span
          className={cx('fa', 'fa-caret-down', styles.arrow,
            { [styles.expanded]: this.state.expanded })}
        />
      </div>);

    const text = <div className={cx(styles.tokenTitle, styles.checkbox,
      { [styles.disabled]: disabled })}>{filter.value}</div>;

    const rowRenderer = ({ index, key, style, parent }) => {
      const item = filteredKeys[index];

      return (
        <CellMeasurer
          cache={this.cache}
          columnIndex={0}
          key={key}
          rowIndex={index}
          parent={parent}
        >
          <div style={style}>
            <div
              className={cx(styles.checkboxRow, { [styles.focusedItem]: focusedItem === index, [styles.disabled]: disabled })}
              data-filter-key={filter.key}
              data-key={item.key}
              data-qa={`filter-options-checkbox-${item.key}`}
              onClick={this.onCheckOne}
              onMouseDown={evt => evt.preventDefault()}
              onMouseMove={() => this.onHoverItem(index)}
            >
              <div className={cx(styles.checkboxBox,
                { [styles.checked]: (selectedKeys[item.key] || false) })}
              >
                {selectedKeys[item.key] ? checkboxCheckedIcon : checkboxIcon}
              </div>
              <div className={styles.checkboxValue}>{item.value}</div>
            </div>
          </div>
        </CellMeasurer>
      );
    };

    const emptyMessage = filteredKeys.length === 0
      ? (<div className={styles.emptyMessage}>
        {I18n.t('views.global.no_items')}
      </div>)
      : null;

    let selectAllCheckbox = checkboxIcon;

    if (isAllChecked) {
      selectAllCheckbox = checkboxCheckedIcon;
    } else if (Object.values(selectedKeys).includes(true)) {
      selectAllCheckbox = checkboxIndeterminateIcon;
    }

    const controls = filteredKeys.length > 0
      ? (
        <div
          className={styles.filterCheckboxControls}
          data-qa="filter-options-checkbox-select-all"
          onClick={this.onCheckAll}
          onMouseDown={evt => evt.preventDefault()}
        >
          <div className={cx(styles.checkboxBox)}>
            {selectAllCheckbox}
          </div>
          <div
            className={styles.filterCheckboxControl}
            data-key={filter.key}
            data-qa={`filter-options-checkbox-${filter.key}`}
          >
            {filteredKeys.length < data.length
              ? I18n.t('views.global.select_results')
              : I18n.t('views.global.select_all')}
          </div>
        </div>
      )
      : null;

    const searchOrClearIcon = (this.state.value.length === 0)
      ? (
        <div
          className={styles.filterCheckboxSearchIcon}
          onMouseDown={evt => evt.preventDefault()}
        >
          {searchIcon}
        </div>
      ) : (
        <div
          className={styles.filterCheckboxSearchIcon}
          onClick={this.onClearSearch}
        >
          {clearIcon}
        </div>
      );

    const search = data.length > 0
      ? (
        <div
          className={styles.filterCheckboxSearch}
          onClick={evt => evt.stopPropagation()}
        >
          <input
            className={styles.filterCheckboxInput}
            data-key={filter.key}
            onChange={this.onSearch}
            onKeyDown={this.onKeyDown}
            placeholder={I18n.t('views.global.search')}
            ref={(el) => { this.searchInput = el; }}
          />
          {searchOrClearIcon}
        </div>
      )
      : null;

    return (
      <div
        className={cx(styles.tokenContainer, styles.checkboxDropdownContainer)}
        data-qa={`filter-options-checkbox-${filter.key}`}
        onClick={this.onContainerClick}
        ref={(el) => { this.container = el; }}
      >
        <div
          className={
            cx(styles.tokenHead, styles.filterCheckboxHead, {
              [styles.expanded]: expanded,
              [styles.disabled]: disabled
            })
          }
          data-key={filter.key}
        >
          {remove}
          {text}
          {formattedCount}
          {caret}
          {spinner}
        </div>
        <div
          className={cx(styles.tokenBody, { [styles.expanded]: expanded })}
          ref={(el) => { this.body = el; }}
        >
          <div className={cx(styles.tokenBodyStatic), { [styles.disabled]: disabled }}>
            {search}
            {controls}
          </div>
          {filteredKeys.length > 0 ?
            <div
              className={cx(styles.tokenBodyScroll, styles.tokenBodyCheckbox, { [styles.disabled]: disabled })}
              ref={(el) => { this.tokenBodyScroll = el; }}
              style={{ height: this.state.containerHeight }}
            >
              <AutoSizer>
                {({ height, width }) => {
                  return (
                    <List
                      onRowsRendered={this.onRowsRendered}
                      deferredMeasurementCache={this.cache}
                      width={width}
                      height={height}
                      rowCount={Math.min(filteredKeys.length)}
                      rowHeight={this.cache.rowHeight}
                      rowRenderer={rowRenderer}
                      scrollToIndex={scrollToIndex}
                    />
                  );
                }}
              </AutoSizer>
            </div>
            :
            emptyMessage
          }
        </div>
      </div>
    );
  }
}

export default I18nDecorator.withConsumer(FilterCheckbox);
