import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, intlShape, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import Immutable from 'immutable';
import TitleBar from '../sections/TitleBar';
import { canCreate } from '../../helpers/permissionHelpers';
import { isLocked } from '../../helpers/workflowStateHelpers';
import styles from '../../../styles/cspace-ui/CreatePage.css';
import panelStyles from '../../../styles/cspace-ui/CreatePagePanel.css';

const serviceTypes = ['object', 'procedure', 'authority'];

const messages = defineMessages({
  title: {
    id: 'createPage.title',
    defaultMessage: 'Create New',
  },
  object: {
    id: 'createPage.object',
    defaultMessage: 'Objects',
  },
  procedure: {
    id: 'createPage.procedure',
    defaultMessage: 'Procedures',
  },
  authority: {
    id: 'createPage.authority',
    defaultMessage: 'Authorities',
  },
});

const tagMessages = defineMessages({
  nagpra: {
    id: 'createPage.tag.nagpra',
    defaultMessage: 'NAGPRA Procedures',
  },
  legacy: {
    id: 'createPage.tag.legacy',
    defaultMessage: 'Legacy Procedures',
  },
});

const getRecordTypesByServiceType = (recordTypes, perms, intl) => {
  const recordTypesByServiceType = {};

  serviceTypes.forEach((serviceType) => {
    const recordTypeNames = Object.keys(recordTypes)
      .filter((recordTypeName) => {
        const recordTypeConfig = recordTypes[recordTypeName];

        return (
          recordTypeConfig.serviceConfig.serviceType === serviceType
          && !recordTypeConfig.disabled
          && canCreate(recordTypeName, perms)
        );
      })
      .sort((nameA, nameB) => {
        const configA = recordTypes[nameA];
        const configB = recordTypes[nameB];

        // Primary sort by sortOrder

        let sortOrderA = configA.sortOrder;
        let sortOrderB = configB.sortOrder;

        if (typeof sortOrderA !== 'number') {
          sortOrderA = Number.MAX_VALUE;
        }

        if (typeof sortOrderB !== 'number') {
          sortOrderB = Number.MAX_VALUE;
        }

        if (sortOrderA !== sortOrderB) {
          return (sortOrderA > sortOrderB ? 1 : -1);
        }

        // Secondary sort by label

        const labelA = intl.formatMessage(configA.messages.record.name);
        const labelB = intl.formatMessage(configB.messages.record.name);

        // FIXME: This should be locale aware
        return labelA.localeCompare(labelB);
      });

    recordTypesByServiceType[serviceType] = recordTypeNames;
  });

  return recordTypesByServiceType;
};

const getVocabularies = (recordTypeConfig, intl, getAuthorityVocabWorkflowState) => {
  const recordTypeName = recordTypeConfig.name;
  const { vocabularies } = recordTypeConfig;

  let vocabularyNames;

  if (vocabularies) {
    vocabularyNames = Object.keys(vocabularies)
      .filter((vocabularyName) => {
        // Filter out vocabularies that don't exist in the services layer, vocabularies that are
        // locked, and vocabularies that are disabled. Always include the 'all' vocabulary.

        const workflowState = getAuthorityVocabWorkflowState(recordTypeName, vocabularyName);

        return (
          vocabularyName !== 'all'
          && workflowState // Empty workflow state means vocab doesn't exist.
          && !isLocked(workflowState)
          && !vocabularies[vocabularyName].disabled
        );
      })
      .sort((nameA, nameB) => {
        const configA = vocabularies[nameA];
        const configB = vocabularies[nameB];

        // Primary sort by sortOrder

        let sortOrderA = configA.sortOrder;
        let sortOrderB = configB.sortOrder;

        if (typeof sortOrderA !== 'number') {
          sortOrderA = Number.MAX_VALUE;
        }

        if (typeof sortOrderB !== 'number') {
          sortOrderB = Number.MAX_VALUE;
        }

        if (sortOrderA !== sortOrderB) {
          return (sortOrderA > sortOrderB ? 1 : -1);
        }

        // Secondary sort by label

        const labelA = intl.formatMessage(configA.messages.name);
        const labelB = intl.formatMessage(configB.messages.name);

        // FIXME: This should be locale aware
        return labelA.localeCompare(labelB);
      });
  }

  return vocabularyNames;
};

const renderListItem = (recordType, config) => {
  const recordConfig = config[recordType];
  const recordDisplayName = <FormattedMessage {...recordConfig.messages.record.name} />;
  const recordLink = <Link id={recordType} to={`/record/${recordType}`}>{recordDisplayName}</Link>;
  return (
    <li key={recordType}>
      {recordLink}
    </li>
  );
};

/**
 * Render the panel for a group of Procedures
 *
 * @param {String} serviceType the name of the service type (object, procedure, authority)
 * @param {Array} items the array of list items to display for the service
 * @returns
 */
const renderPanel = (serviceType, items) => (
  items && items.length > 0 ? (
    <div className={panelStyles[serviceType]} key={serviceType}>
      <h2><FormattedMessage {...messages[serviceType]} /></h2>
      <ul>
        {items}
      </ul>
    </div>
  ) : null
);

/**
 * Render the div for Object records
 *
 * @param {Array} recordTypes the object records
 * @param {Object} config the cspace config
 * @returns the div
 */
const renderObjects = (recordTypes = [], config) => {
  const serviceType = 'object';
  const items = recordTypes.map((recordType) => renderListItem(recordType, config));
  return renderPanel(serviceType, items);
};

/**
 * Render the div for procedure records. The procedures are grouped together by their service tags
 * in order to display procedures in a workflow together. Each tag has its own header in order to
 * act as a delimiter within the div. Procedures without a tag do not have a header and are part
 * of a default group.
 *
 * @param {Array} recordTypes the procedure record types
 * @param {Object} config the cspace config
 * @param {Function} getTagsForRecord function to query the service tag of a record
 * @param {Object} tagConfig the configuration for the service tags containing their sortOrder
 * @returns
 */
const renderProcedures = (recordTypes = [], config, getTagsForRecord, tagConfig) => {
  const serviceType = 'procedure';

  const grouped = {};
  recordTypes.forEach((recordType) => {
    const tag = getTagsForRecord(recordType) || 'defaultGroup';
    if (grouped[tag] === undefined) {
      grouped[tag] = [recordType];
    } else {
      grouped[tag].push(recordType);
    }
  });

  const {
    defaultGroup: defaultRecordTypes = [],
    ...taggedRecordTypes
  } = grouped;

  const defaultItems = defaultRecordTypes.map((recordType) => renderListItem(recordType, config));

  const taggedItems = Object.keys(taggedRecordTypes).sort((lhs, rhs) => {
    const lhsConfig = tagConfig[lhs] || {};
    const rhsConfig = tagConfig[rhs] || {};

    const {
      sortOrder: lhsOrder = Number.MAX_SAFE_INTEGER,
    } = lhsConfig;

    const {
      sortOrder: rhsOrder = Number.MAX_SAFE_INTEGER,
    } = rhsConfig;

    return lhsOrder > rhsOrder ? 1 : -1;
  }).map((tag) => {
    const tagRecordTypes = taggedRecordTypes[tag];
    const items = tagRecordTypes.map((recordType) => renderListItem(recordType, config));

    return (
      <li className={panelStyles.tag}>
        <h3 id={tag}><FormattedMessage {...tagMessages[tag]} /></h3>
        <ul>
          {items}
        </ul>
      </li>
    );
  });

  return renderPanel(serviceType, defaultItems.concat(taggedItems));
};

/**
 * Render the div for creating authority items. Each authority is a header and its vocabulary items
 * are represented as a sub-list.
 *
 * @param {Array} recordTypes the authority records
 * @param {Object} config the cspace config
 * @param {intlShape} intl the intl object
 * @param {Function} getAuthorityVocabWorkflowState function to get workflow states
 */
const renderAuthorities = (recordTypes = [], config, intl, getAuthorityVocabWorkflowState) => {
  const authorityItems = recordTypes.map((recordType) => {
    const recordConfig = config[recordType];
    const vocabularies = getVocabularies(
      recordConfig, intl, getAuthorityVocabWorkflowState,
    );

    if (!vocabularies || vocabularies.length === 0) {
      return null;
    }

    const vocabularyItems = vocabularies.map((vocabulary) => (
      <li key={vocabulary}>
        <Link id={`${recordType}/${vocabulary}`} to={`/record/${recordType}/${vocabulary}`}>
          <FormattedMessage {...recordConfig.vocabularies[vocabulary].messages.name} />
        </Link>
      </li>
    ));
    const vocabularyList = <ul>{vocabularyItems}</ul>;

    const recordDisplayName = <FormattedMessage {...recordConfig.messages.record.name} />;
    const recordLink = <h3 id={recordType}>{recordDisplayName}</h3>;

    return (
      <li key={recordType}>
        {recordLink}
        {vocabularyList}
      </li>
    );
  });

  return renderPanel('authority', authorityItems);
};

const contextTypes = {
  config: PropTypes.shape({
    recordTypes: PropTypes.object,
  }),
};

const propTypes = {
  intl: intlShape,
  perms: PropTypes.instanceOf(Immutable.Map),
  getAuthorityVocabWorkflowState: PropTypes.func,
  getTagsForRecord: PropTypes.func,
};

const defaultProps = {
  getAuthorityVocabWorkflowState: () => null,
};

export default function CreatePage(props, context) {
  const {
    intl,
    perms,
    getAuthorityVocabWorkflowState,
    getTagsForRecord,
  } = props;

  const {
    config,
  } = context;

  const {
    tags: tagConfig,
    recordTypes,
  } = config;

  let objectPanel;
  let procedurePanel;
  let authorityPanel;

  if (recordTypes) {
    const recordTypesByServiceType = getRecordTypesByServiceType(recordTypes, perms, intl);

    objectPanel = renderObjects(recordTypesByServiceType.object, recordTypes);

    procedurePanel = renderProcedures(recordTypesByServiceType.procedure,
      recordTypes,
      getTagsForRecord,
      tagConfig);

    authorityPanel = renderAuthorities(recordTypesByServiceType.authority,
      recordTypes,
      intl,
      getAuthorityVocabWorkflowState);
  }

  const title = <FormattedMessage {...messages.title} />;

  return (
    <div className={styles.common}>
      <TitleBar title={title} updateDocumentTitle />

      <div>
        {objectPanel}
        {procedurePanel}
        {authorityPanel}
      </div>
    </div>
  );
}

CreatePage.propTypes = propTypes;
CreatePage.defaultProps = defaultProps;
CreatePage.contextTypes = contextTypes;
