import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import classNamesBind from 'classnames/bind';
import ThemeContext from 'terra-theme-context';
import memoize from 'memoize-one';
import { KEY_RETURN, KEY_SPACE } from 'keycode-js';

import styles from './Cell.module.scss';

const cx = classNamesBind.bind(styles);

const propTypes = {
  /**
   * Accessible text provided for the Cell.
   */
  ariaLabel: PropTypes.string,
  /**
   * String identifier of the section in which the Cell will be rendered.
   */
  sectionId: PropTypes.string.isRequired,
  /**
   * String identifier of the row in which the Cell will be rendered.
   */
  rowId: PropTypes.string.isRequired,
  /**
   * String identifier of the column in which the Cell will be rendered.
   */
  columnId: PropTypes.string.isRequired,
  /**
   * String-formatted width that the Cell should be rendered as. Values are suggested to be in `rem`s (ex `'5rem'`), but any valid CSS height value is accepted.
   */
  width: PropTypes.string.isRequired,
  /**
   * Boolean indicating whether the Cell is selectable.
   */
  isSelectable: PropTypes.bool,
  /**
   * Boolean indicating whether the Cell is actively selected.
   */
  isSelected: PropTypes.bool,
  /**
   * Function that will be called upon Cell selection. The `isSelectable` prop must be true for this function to be called.
   * Parameters: `onSelect(sectionId, rowId, columnId)`
   */
  onSelect: PropTypes.func,
  /**
   * Function that will be called upon the mouse entering the selectable region of the Cell. The `isSelectable` prop must be true for this function to be called.
   * Parameters: `onHoverStart(event)`
   */
  onHoverStart: PropTypes.func,
  /**
   * Function that will be called upon the mouse leaving the selectable region of the Cell. The `isSelectable` prop must be true for this function to be called.
   * Parameters: `onHoverEnd(event)`
   */
  onHoverEnd: PropTypes.func,
  /**
   * Content that will rendered within the Cell.
   */
  children: PropTypes.node,
  /**
   * Function that will be called with a ref to the Cell's selectable element. Parameters: `selectableRefCallback(selectableRef)`
   */
  selectableRefCallback: PropTypes.func,
  /**
   * Boolean indicating whether the cell should be highlighted with background styling.
   */
  isColumnHighlighted: PropTypes.bool,
  /**
   * Boolean indicating whether the cell is in the first row, also used with column highlight background styling.
   */
  isFirstRow: PropTypes.bool,
  /**
   * Boolean indicating whether the cell is in the last row, also used with column highlight background styling.
   */
  isLastRow: PropTypes.bool,
};

class Cell extends React.Component {
  constructor(props) {
    super(props);

    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleTargetClick = this.handleTargetClick.bind(this);

    this.getCellStyles = memoize(this.getCellStyles);
  }

  handleKeyDown(event) {
    if (event.nativeEvent.keyCode === KEY_RETURN || event.nativeEvent.keyCode === KEY_SPACE) {
      const { onSelect } = this.props;

      if (onSelect) {
        event.preventDefault();
        onSelect(this.props.sectionId, this.props.rowId, this.props.columnId);
      }
    }
  }

  handleTargetClick() {
    const { onSelect } = this.props;

    if (onSelect) {
      onSelect(this.props.sectionId, this.props.rowId, this.props.columnId);
    }
  }

  /* eslint-disable class-methods-use-this */
  /**
   * This function is memoized in the constructor so that for a given width value, the same object reference will be returned.
   * This allows for repeat renders of the Cell to occur more efficiently if the width value has not changed between renders.
   */
  getCellStyles(width) {
    return {
      width,
    };
  }
  /* eslint-enable class-methods-use-this */

  render() {
    const {
      sectionId,
      rowId,
      columnId,
      isSelectable,
      isSelected,
      width,
      onSelect,
      children,
      selectableRefCallback,
      onHoverStart,
      onHoverEnd,
      ariaLabel,
      isColumnHighlighted,
      isFirstRow,
      isLastRow,
      ...customProps
    } = this.props;

    /* eslint-disable react/forbid-dom-props, jsx-a11y/no-static-element-interactions */
    const theme = this.context;
    const role = isSelectable ? 'button' : undefined;
    const tabIndex = isSelectable ? '0' : undefined;

    const highlightCellClassNames = !isSelected && isColumnHighlighted && cx([
      { highlighted: !isSelectable },
      { 'highlighted-selectable': isSelectable },
      { first: isFirstRow },
      { last: isLastRow },
    ]);

    return (
      <div
        {...customProps}
        className={classNames(cx('container', theme.className), customProps.className)}
        style={this.getCellStyles(width)}
        aria-selected={isSelected ? true : undefined}
      >
        <div
          role={role}
          className={cx([
            'content',
            highlightCellClassNames,
            { selectable: isSelectable },
            { selected: isSelected },
          ])}
          onClick={isSelectable ? this.handleTargetClick : undefined}
          onKeyDown={isSelectable ? this.handleKeyDown : undefined}
          onMouseEnter={onHoverStart}
          onMouseLeave={onHoverEnd}
          tabIndex={tabIndex}
          ref={selectableRefCallback}
          aria-label={ariaLabel}
        >
          {children}
        </div>
      </div>
    );
    /* eslint-enable react/forbid-dom-props, jsx-a11y/no-static-element-interactions */
  }
}

Cell.propTypes = propTypes;
Cell.contextType = ThemeContext;

export default Cell;
