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 Spinner from '@procore/core-react/lib/components/Spinner'

import { FILTERS_ANALYTICS } from './Filters'

import {
  checkboxIcon,
  checkboxCheckedIcon,
  checkboxCheckedDisabledIcon,
  checkboxIndeterminateIcon,
  clearIcon,
  searchIcon,
} from './FilterIcons'

const matchSublocation = (parentLocationName, itemName) => {
  return (
    itemName.startsWith(`${parentLocationName} >`) ||
    itemName.startsWith(`${parentLocationName}>`)
  );
};

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

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

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

    this.ignoreBlur = false

    this.memoizedData = []

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

    this.state = {
      data: [],
      disabledKeys: [],
      containerHeight: props.filterCheckboxRowHeight,
      checkAllIsLoading: false,
      expanded: false,
      focusedItem: 0,
      hasLoaded: false,
      filteredKeys: [],
      isAllChecked: false,
      isIncludeSublocations: props.isIncludeSublocations,
      isLoading: 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 => {
    this.memoizedData = data.reduce(
      (acc, { key, value }) => Object.assign({}, acc, { [key]: value }),
      {}
    )

    this.setState(
      {
        data: data || [],
        expanded: true,
        filteredKeys: data || [],
        isLoading: false,
        hasLoaded: true,
      },
      () => {
        this.searchInput && this.searchInput.focus()
        if (this.state.isIncludeSublocations) {
          this.setState({
            disabledKeys: this.findSelectedSublocations(
              this.state.selectedKeys
            ),
          })
        }
      }
    )
  }

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

    this.setState({ expanded: false, isLoading: false }, () => {
      const { isIncludeSublocations, selectedKeys } = this.state
      const mergedOptions = {
        ...this.props.options,
        isIncludeSublocations,
      }
      const keys = Object.keys(this.state.selectedKeys).reduce(
        (acc, k) => (selectedKeys[k] ? acc.concat(k) : acc),
        []
      )

      this.props.onSave(this.props.filter, keys, mergedOptions)
    })
  }

  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 {
      disabledKeys,
      filteredKeys,
      isIncludeSublocations,
      selectedKeys,
    } = this.state

    let key = evt.currentTarget.dataset.key
    let value = evt.currentTarget.dataset.value

    if (evt.key === 'Enter') {
      key = this.state.filteredKeys[this.state.focusedItem].key
      value = this.state.filteredKeys[this.state.focusedItem].value
    }

    if (disabledKeys[key]) {
      return
    }

    selectedKeys[key] = !selectedKeys[key]

    if (isIncludeSublocations && !this.state.isAllChecked) {
      this.state.filteredKeys.forEach(item => {
        if (matchSublocation(value, this.memoizedData[item.key])) {
          if (`${item.key}` === `${key}`) {
            selectedKeys[item.key] = selectedKeys[key];
          } else {
            disabledKeys[item.key] = selectedKeys[key];
          }
        }
      })
    }

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

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

  onCheckAll = evt => {
    evt.stopPropagation()

    const { filteredKeys, isAllChecked } = this.state

    if (isAllChecked) {
      this.props.onAnalytics(FILTERS_ANALYTICS.CLEAR_ALL)

      this.setState({
        isAllChecked: !isAllChecked,
        selectedKeys: {},
        disabledKeys: [],
      })
    } else if (filteredKeys.length > 1000) {
      this.setState({ checkAllIsLoading: true })

      // Temporary until we have pagination. Starts to lag over 1000 items.
      setTimeout(() => {
        this.checkAllLocations()
      }, 10)
    } else {
      this.checkAllLocations()
    }
  }

  checkAllLocations = () => {
    const { filteredKeys, isAllChecked, isIncludeSublocations } = this.state

    const selectedKeys = filteredKeys.reduce(
      (acc, d) => Object.assign({}, acc, { [d.key]: true }),
      {}
    )
    const disabledKeys = isIncludeSublocations
      ? this.findSelectedSublocations(selectedKeys)
      : []
    this.props.onAnalytics(FILTERS_ANALYTICS.SELECT_ALL)

    this.setState({
      checkAllIsLoading: false,
      isAllChecked: !isAllChecked,
      selectedKeys,
      disabledKeys,
    })
  }

  onIncludeSublocations = evt => {
    evt.stopPropagation()
    const keys = this.state.isIncludeSublocations
      ? []
      : this.findSelectedSublocations(this.state.selectedKeys)
    this.setState({
      isIncludeSublocations: !this.state.isIncludeSublocations,
      disabledKeys: keys,
    })
  }

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

    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 })

  findSelectedSublocations(selectedKeys) {
    const { disabledKeys, filteredKeys } = this.state

    const selected = Object.keys(selectedKeys).filter(
      key => selectedKeys[key] === true
    )

    selected.forEach(key => {
      filteredKeys.forEach(item => {
        if (
          matchSublocation(this.memoizedData[key], this.memoizedData[item.key])
        ) {
          if (parseInt(item.key) !== parseInt(key)) {
            disabledKeys[item.key] = selectedKeys[key];
          }
        }
      })
    })
    return disabledKeys
  }

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

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

  checkedIcon = key => {
    const { selectedKeys, disabledKeys } = this.state
    if (selectedKeys[key]) {
      return checkboxCheckedIcon
    } else if (disabledKeys[key]) {
      return checkboxCheckedDisabledIcon
    }
    return checkboxIcon
  }

  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,
    } = this.props

    const {
      data,
      disabledKeys,
      checkAllIsLoading,
      expanded,
      filteredKeys,
      focusedItem,
      isAllChecked,
      isLoading,
      isIncludeSublocations,
      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]: this.props.disabled })}>({count})</div>
      : null);

    const remove = (
      <button
        disabled={this.props.disabled}
        className={cx(styles.tokenRemove,
          {
            [styles.expanded]: expanded,
            [styles.disabled]: this.props.disabled
          })}
        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]: this.props.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]: this.props.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,
              })}
              data-filter-key={filter.key}
              data-key={item.key}
              data-qa={`filter-options-location-${item.key}`}
              data-value={item.value}
              key={item.key}
              onClick={this.onCheckOne}
              onMouseDown={evt => evt.preventDefault()}
              onMouseMove={() => this.onHoverItem(index)}
            >
              <div
                className={cx(styles.checkboxBox, {
                  [styles.checked]:
                    selectedKeys[item.key] || disabledKeys[item.key] || false,
                  [styles.checkboxDisabled]: disabledKeys[item.key],
                })}
              >
                {this.checkedIcon(item.key)}
              </div>
              <div
                className={cx(styles.checkboxValue, {
                  [styles.checkboxDisabled]: disabledKeys[item.key],
                })}
              >
                {item.value}
              </div>
            </div>
          </div>
        </CellMeasurer>
      )
    }

    const loadingOverlay = (
      <div className={styles.spinnerOverlay}>
        <Spinner
          className={styles.spinnerOverlay__spinner}
          label=""
          loading={checkAllIsLoading}
          size="md"
        />
      </div>
    )

    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
    }

    let includeSublocationsCheckbox = checkboxIcon
    if (isIncludeSublocations) {
      includeSublocationsCheckbox = checkboxCheckedIcon
    }

    let controls = null
    if (filteredKeys.length > 0) {
      controls = [
        <div
          className={styles.filterCheckboxControls}
          data-qa="filter-options-location-select-all"
          key="select-all-control"
          onClick={this.onCheckAll}
          onMouseDown={evt => evt.preventDefault()}
        >
          <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>,
        <div
          className={styles.filterCheckboxControls}
          data-qa="filter-options-sublocation-select-all"
          key="sublocations-control"
          onClick={this.onIncludeSublocations}
          onMouseDown={evt => evt.preventDefault()}
        >
          <div className={cx(styles.checkboxBox)}>
            {includeSublocationsCheckbox}
          </div>
          <div className={styles.filterCheckboxControl} data-key={filter.key}>
            {I18n.t('views.global.include_sublocations')}
          </div>
        </div>,
      ]
    }

    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)}
        onClick={this.onContainerClick}
        ref={el => {
          this.container = el
        }}
      >
        <div
          className={cx(styles.tokenHead, styles.filterCheckboxHead, {
            [styles.expanded]: expanded,
            [styles.disabled]: this.props.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
              className={cx(styles.tokenBodyScroll, styles.tokenBodyCheckbox)}
              ref={el => {
                this.tokenBodyScroll = el
              }}
              style={{ height: this.state.containerHeight }}
            >
              {checkAllIsLoading && loadingOverlay}

              <AutoSizer>
                {({ height, width }) => (
                  <List
                    onRowsRendered={this.onRowsRendered}
                    deferredMeasurementCache={this.cache}
                    width={width}
                    height={height}
                    rowCount={filteredKeys.length}
                    rowHeight={this.cache.rowHeight}
                    rowRenderer={rowRenderer}
                    scrollToIndex={scrollToIndex}
                  />
                )}
              </AutoSizer>
            </div>
          ) : (
              emptyMessage
            )}
        </div>
      </div>
    )
  }
}

export default I18nDecorator.withConsumer(FilterLocation)
