import PropTypes from 'prop-types';
import {
  always,
  append,
  contains,
  filter,
  isEmpty,
  isNil,
  map,
  omit,
  without,
} from 'ramda';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Label, Loader } from 'semantic-ui-react';

import { withSite } from '../../addons/Site';

/* Interface */
const propTypes = {
  /**
   * Function to compute props for child `<Label>`s.
   * @param {boolean} selected - Whether the label is selected.
   * @returns {Object} Desired props for the `<Label>`.
   */
  computeLabelProps: PropTypes.func.isRequired,

  /** Predicate to restrict which items show up. */
  filterPredicate: PropTypes.func,

  /** A `TermChooser` can allow multiple selections. */
  multiple: PropTypes.bool,

  /**
   * Called when the user attempts to change the value.
   * @param {SyntheticEvent} event - React's original SyntheticEvent.
   * @param {Object} data - All props and proposed value.
   */
  onChange: PropTypes.func.isRequired,

  /** Content to display while awaiting data. */
  placeholder: PropTypes.element,

  /**
   * StoryShare `Site`.
   * @private
   */
  site: PropTypes.object.isRequired,

  /** Slug for taxonomy from which terms are drawn. */
  taxonomySlug: PropTypes.string.isRequired,

  /**
   * Loaded terms.
   * @private
   */
  terms: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
  }).isRequired),

  /** Current value or value array if multiple. */
  value: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.arrayOf(PropTypes.number.isRequired),
  ]),
};

/* Defaults */
const defaultProps = {
  computeLabelProps: (selected) => ({ basic: !selected }),
  filterPredicate: always(true),
  placeholder: <Loader active inline />,
};

/**
 * Parameters for use when requesting taxonomy terms
 */
const REQUEST_PARAMS = { per_page: 100 };

/**
 * Interface for choosing terms from a taxonomy.
 * Supports up to 100 terms.
 * @returns {Object} component
 */
export class TermChooser extends Component {
  /**
   * Loads taxonomy terms when the component mounts
   * Note that only the first 100 terms will be displayed.
   * @returns {void}
   */
  componentDidMount = () => {
    const { site, taxonomySlug } = this.props;
    site.req(taxonomySlug)
      .read(REQUEST_PARAMS);
  };

  /**
   * Handles clicks on labels
   * @param {SyntheticEvent} event - React's synthetic event.
   * @param {Object} data - All props.
   * @returns {void}
   */
  handleClick = (event, { value: id }) => {
    const { multiple, onChange, value: oldValue } = this.props;
    const passThroughProps = omit(['site', 'terms'], this.props);

    if (multiple) {
      const value = contains(id, oldValue || [])
        ? without([id], oldValue)
        : append(id, oldValue);

      onChange(event, { ...passThroughProps, value });
    } else {
      if (id !== oldValue) {
        onChange(event, { ...passThroughProps, value: id });
      }
    }
  };

  render = () => {
    const {
      computeLabelProps,
      filterPredicate,
      multiple,
      placeholder,
      terms,
      value,
    } = this.props;

    if (isNil(terms) || isEmpty(terms)) {
      return placeholder;
    }

    return (
      <Label.Group>
        { map(({ id, name }) => (
          <Label
            as="a"
            id={ id }
            key={ id }
            onClick={ this.handleClick }
            value={ id }
            { ...computeLabelProps(
              multiple ? contains(id, value || []) : id === value
            ) }
          >
            <span dangerouslySetInnerHTML={ { __html: name } } />
          </Label>
        ), filter(filterPredicate, terms)) }
      </Label.Group>
    );
  };
}

TermChooser.propTypes = propTypes;
TermChooser.defaultProps = defaultProps;

export const mapStateToProps = (_, { site, taxonomySlug }) => ({
  terms: site.getCollectionByRoute(taxonomySlug, REQUEST_PARAMS),
});

// n.b. ordering: mapStateToProps needs site
export default withSite(connect(mapStateToProps)(TermChooser));
