import PropTypes from 'prop-types';
import { equals, map, pick, isEmpty } from 'ramda';
import React, { Component } from 'react';
import { connect } from 'react-redux';

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

/* Interface */
const propTypes = {
  /** Function to call with returned results (see `mode`). */
  children: PropTypes.func,

  /**
   * Loaded collection.
   * @private
   */
  collection: PropTypes.arrayOf(PropTypes.object),

  /** Content to display if there are no data to display. */
  emptyContent: PropTypes.element,

  /**
   * Mode of operation - either `'items'` (the default) or `'collection'`.
   * When in `'items'` mode the `children` function will be called with each
   * loaded post or term. It should return JSX that has a `key` prop on the
   * root.
   * When in `'collection'` mode the `children` function will be called once
   * with the whole collection:
   * @param `(items: Object[], pagination)`
   */
  mode: PropTypes.oneOf(['items', 'collection']),

  /**
   * Function to call when collection has loaded.
   * @param {Object} pagination - Counts of items and pages.
   * @param {number} pagination.total - Item count for the collection.
   * @param {number} pagination.pages - Page count for the collection.
   */
  onUpdate: PropTypes.func,

  /**
   * Pagination data returned by StoryShare.
   * @private
   */
  pagination: PropTypes.shape({
    total: PropTypes.number.isRequired,
    totalPages: PropTypes.number.isRequired,
  }),

  /** Additional parameters to include in the API request. */
  params: PropTypes.object,

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

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

  /** Slug for post type or taxonomy from which posts or terms are drawn. */
  typeSlug: PropTypes.string.isRequired,
};

/* Defaults */
const defaultProps = {
  children: () => null,
  mode: 'items',
  placeholder: null,
  emptyContent: null,
};

/**
 * Loads a collection from StoryShare
 * Should be given a functional child which will be called with each item
 * returned when the collection loads.
 * @returns {Object} component
 */
export class Collection extends Component {
  /**
   * Performs actions when the component mounts.
   * @returns {void}
   */
  componentDidMount = () => {
    // Request the collection
    this.fetchData();

    // Call `onUpdate` with any pagination information
    // We need to do this here in case the collection is already cached.
    // If it is, pagination data will be available immediately and
    // `componentDidUpdate` will not be called.
    const { onUpdate, pagination } = this.props;
    if (onUpdate && pagination) {
      onUpdate(pagination);
    }
  }

  /**
   * Performs actions in response to updates.
   * @param {Object} prevProps - Previous props values.
   * @returns {void}
   */
  componentDidUpdate = (prevProps) => {
    // Request the collection again if route or request parameters have changed
    const fetchPicker = pick(['params', 'typeSlug']);
    if (!equals(fetchPicker(this.props), fetchPicker(prevProps))) {
      this.fetchData();
    }

    // Call `onUpdate` with pagination information
    if (this.props.onUpdate) {
      const loadedPicker = pick(['onUpdate', 'pagination']);
      if (!equals(loadedPicker(this.props), loadedPicker(prevProps))) {
        this.props.onUpdate(this.props.pagination);
      }
    }
  };

  /**
   * Requests data from the StoryShare instance.
   * @returns {void}
   */
  fetchData = () => {
    const { params, site, typeSlug } = this.props;
    site.req(typeSlug)
      .setParams(params)
      .ttl(false)
      .read();
  };

  render = () => {
    const {
      children,
      collection,
      emptyContent,
      mode,
      pagination,
      placeholder,
    } = this.props;

    if (!collection) {
      return placeholder;
    }

    if (isEmpty(collection) && emptyContent) {
      return emptyContent;
    }

    switch (mode) {
      case 'items':
        return <>{ map(children, collection) }</>;
      case 'collection':
        return children(collection, pagination);
      default:
        return null; // Unknown mode: error will be logged as propTypes warning
    }
  };
}

Collection.defaultProps = defaultProps;
Collection.propTypes = propTypes;

export const mapStateToProps = (_, { params, site, typeSlug }) => {
  const response = site.getResponseByRoute(typeSlug, params);
  if (!response) {
    return { };
  }

  return {
    collection: response.body,
    pagination: {
      total: response.total,
      totalPages: response.totalPages,
    },
  };
};

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