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 { FILTERS_ANALYTICS } from './Filters';
import {
  checkboxIcon,
  checkboxCheckedIcon,
  checkboxIndeterminateIcon,
  clearIcon,
  searchIcon
} from './FilterIcons';

export class FilterCategoryCheckbox 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(),
    disabled: PropTypes.bool
  }

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

  constructor(props) {
    super(props);
    const { selected } = this.props;
    const selectedKeys = selected.reduce((acc, v) => Object.assign({}, acc, { [v]: true }), {});

    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) {
    if (nextProps.focusedDropdown && nextProps.focusedDropdownFilters) {
      const isFocused = nextProps.focusedDropdown.key === nextProps.filter.key;

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

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

  onComplete = (data) => {
    let searchableData = [];

    data.forEach((category) => {
      searchableData = searchableData.concat(category.children);
    });

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

  onBlur = () => {
    if (this.ignoreBlur === true) {
      this.ignoreBlur = false;
      return;
    }

    this.setState({ expanded: false, isLoading: false }, () => {
      const { selectedKeys } = this.state;
      const keys = Object.keys(this.state.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);
      });
    }
  }

  getIsAllChecked = (filteredKeys, selectedKeys) => {
    let filteredKeysCount = 0;
    filteredKeys.forEach((category) => {
      filteredKeysCount += category.children.length;
    });

    let selectedKeyCount = 0;
    Object.keys(selectedKeys).forEach((item) => {
      if (selectedKeys[item] === true) {
        selectedKeyCount += 1;
      }
    });

    const isAllChecked = (filteredKeysCount === selectedKeyCount);

    return isAllChecked;
  }

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

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

    if (evt.key === 'Enter') {
      key = focusedItem;
    } else {
      key = evt.currentTarget.dataset.key;
    }

    selectedKeys[key] = !selectedKeys[key];

    const isAllChecked = this.getIsAllChecked(filteredKeys, selectedKeys);

    this.setState({ isAllChecked, selectedKeys });
  }

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

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

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

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

  onCategoryCheckAll = (evt, childrenKeys, isAllCategoryItemsSelected) => {
    evt.stopPropagation();

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

    childrenKeys.forEach((key) => {
      selectedKeys[key] = !isAllCategoryItemsSelected;
    });

    const isAllChecked = this.getIsAllChecked(filteredKeys, selectedKeys);

    this.setState({ isAllChecked, selectedKeys });
  }

  onSearch = (evt) => {
    const { selectedKeys } = this.state;
    const value = evt.target.value.toLowerCase();
    const filteredKeys = [];

    this.cache.clearAll()

    this.state.data.forEach((category) => {
      const children = category.children.filter(item =>
        item.value.toLowerCase().includes(value.toLowerCase() || ''));

      if (children.length > 0) {
        filteredKeys.push({
          ...category,
          children
        });
      }
    });

    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;
    const { focusedItem } = this.state;

    switch (evt.key) {
      case 'Tab':
      case 'Escape':
        this.onBlur();
        break;
      case 'Enter':
        const category = filteredKeys.filter(category => category.key === focusedItem);
        const isCategoryItem = !!category.length;

        if (isCategoryItem) {
          const { selectedKeys } = this.state;
          const { children } = category[0];

          const childrenKeys = children.map(item => item.key);
          const selectedCategoryItems = [];

          Object.keys(selectedKeys).forEach((key) => {
            const currentKey = parseInt(key);
            const isChild = childrenKeys.indexOf(currentKey) !== -1;

            if (isChild && selectedKeys[key] === true) {
              selectedCategoryItems.push(currentKey);
            }
          });

          const isAllCategoryItemsSelected = childrenKeys.length === selectedCategoryItems.length;

          this.onCategoryCheckAll(evt, childrenKeys, isAllCategoryItemsSelected);
        } else {
          this.onCheckOne(evt);
        }

        break;
      default:
        break;
    }
  };

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

  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,
      filterCheckboxRowHeight,
      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}
        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 rowCategoryRenderer = ({ index, key, style, parent }) => {
      if (filteredKeys[index] && filteredKeys[index].children) {
        const categoryItem = filteredKeys[index];
        const { children } = categoryItem;

        const childrenKeys = children.map(item => item.key);
        const selectedCategoryItems = [];

        Object.keys(selectedKeys).forEach((key) => {
          const currentKey = parseInt(key);
          const isChild = childrenKeys.indexOf(currentKey) !== -1;

          if (isChild && selectedKeys[key] === true) {
            selectedCategoryItems.push(currentKey);
          }
        });

        const isAllCategoryItemsSelected = childrenKeys.length === selectedCategoryItems.length;
        const isSomeCategoryItmesSelected =
          selectedCategoryItems.length > 0 &&
          selectedCategoryItems.length < childrenKeys.length;

        let checkbox = checkboxIcon;
        if (isAllCategoryItemsSelected) {
          checkbox = checkboxCheckedIcon;
        } else if (isSomeCategoryItmesSelected) {
          checkbox = checkboxIndeterminateIcon;
        }

        return (
          <CellMeasurer
            cache={this.cache}
            columnIndex={0}
            key={key}
            rowIndex={index}
            parent={parent}
          >
            <div style={style}>
              <div
                className={cx(styles.filterCheckboxControls, { [styles.focusedItem]: focusedItem === categoryItem.key })}
                data-qa="filter-options-category-select-all"
                onMouseDown={evt => evt.preventDefault()}
                onMouseMove={() => this.onHoverItem(categoryItem.key)}
                onClick={evt => this.onCategoryCheckAll(evt, childrenKeys, isAllCategoryItemsSelected)}
              >
                <div className={styles.categoryCheckboxBox}>{checkbox}</div>
                <div className={styles.filterCheckboxControl}>{categoryItem.value}</div>
              </div>

              {children.map((item, index) => {
                const itemKey = `${key}-${index}`;

                return (
                  <div key={itemKey}>
                    <div
                      className={cx(styles.checkboxRow, { [styles.focusedItem]: focusedItem === item.key })}
                      data-filter-key={filter.key}
                      data-qa={`filter-options-category-${item.key}`}
                      data-key={item.key}
                      onClick={this.onCheckOne}
                      onMouseDown={evt => evt.preventDefault()}
                      onMouseMove={() => this.onHoverItem(item.key)}
                    >
                      <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>
                );
              })}

            </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-category-select-all"
          onClick={this.onCheckAll}
          onMouseDown={evt => evt.preventDefault()}
          data-checkbox={'select-all'}
        >
          <div className={cx(styles.checkboxBox)}>
            {selectAllCheckbox}
          </div>
          <div
            className={styles.filterCheckboxControl}
            data-key={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;

    const categoryTopMargin = 12;

    let rowCount = filteredKeys.length;

    filteredKeys.forEach((category) => {
      rowCount += category.children.length;
    });

    return (
      <div
        className={cx(styles.tokenContainer, styles.checkboxDropdownContainer)}
        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)}>
            {search}
            {controls}
          </div>
          {filteredKeys.length > 0 ?
            <div

              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={filteredKeys.length}
                      rowHeight={this.cache.rowHeight}
                      rowRenderer={rowCategoryRenderer}
                      scrollToIndex={scrollToIndex}
                    />
                  )
                }}
              </AutoSizer>
            </div>
            :
            emptyMessage
          }
        </div>
      </div>
    );
  }
}

export default I18nDecorator.withConsumer(FilterCategoryCheckbox);
