'use strict';

import flattenNested from '../../../../Core/flattenNested';
import ActiveConcept from './ActiveConcept';
import OpenInactiveConcept from './OpenInactiveConcept';
import Icon from '../../../Icon.jsx';
import ObserveModelMixin from '../../../ObserveModelMixin';
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import Styles from './summary-concept.scss';

const ADD_FIRST_TEXT = 'Add a condition';
const ADD_MORE_TEXT = 'Add new condition';

/*
 * SummaryConcept displays all the active and open nodes under a given
 * SummaryConcept.
 * It has two "modes":
 *   When summaryConcept.allowMultiple is false, it can be substituted directly for
 *   a regular displayVariableConcept (or array of concepts). Each child can have
 *   zero, one or more items selected or not selected (depending on their allowMultiple setting).
 *   When summaryConcept.allowMultiple is true, it treats the child concepts as
 *   "conditions", any number of which can be added or removed.
 *
 * Parents containing 1 or more active nodes are shown via <./ActiveConcept>.
 *    (They may be open or closed, and ActiveConcept handles the difference.)
 * Open nodes not containing any active nodes are shown via <./OpenInactiveConcept>.
 *    (This is typically the case when a user has pressed the AddButton but yet to
 *    activate any leaf nodes.)
 *    (If summaryConcept.allowMultiple is false, you cannot 'cancel' or 'go back' on this.)
 * If summaryConcept.allowMultiple is true, then an <./AddButton> is also shown,
 *    which simply opens the root concept, at which point OpenInactiveConcept takes over.
 *
 * This design would need revision to handle concepts whose direct children are a mix of
 * both leaf nodes and parent nodes.
 */
const SummaryConcept = createReactClass({
    displayName: 'SummaryConcept',
    mixins: [ObserveModelMixin],

    propTypes: {
        concept: PropTypes.object.isRequired,  // Must be a SummaryConcept.
        isLoading: PropTypes.bool
    },

    render() {
        const concept = this.props.concept;
        // Leaf nodes have either an undefined or a 0-length `items` array.
        const isLeafNode = concept => (!concept.items || concept.items.length === 0);
        const activeLeafNodes = concept.getNodes(isLeafNode).filter(concept => concept.isActive);
        const activeLeafNodesByParent = groupByParentId(activeLeafNodes, parent => parent.id);
        const openDescendantsWithoutActiveChildren = getOpenDescendantsWithoutActiveChildren(concept);
        const isLoading = this.props.isLoading;
        return (
            <div className={Styles.root}>
                <div className={Styles.title}>{concept.name}:</div>
                <For each="group" index="i" of={activeLeafNodesByParent}>
                    <ActiveConcept key={i} rootConcept={concept} activeLeafNodesWithParent={group} isLoading={isLoading}/>
                </For>
                <If condition={activeLeafNodesByParent.length === 0 && openDescendantsWithoutActiveChildren.length === 0}>
                    <div className={Styles.noConditions}>
                        None
                    </div>
                </If>
                <If condition={openDescendantsWithoutActiveChildren.length > 0 && !isLoading}>
                    <OpenInactiveConcept rootConcept={concept} openInactiveConcept={openDescendantsWithoutActiveChildren[0]}/>
                </If>
                <If condition={concept.allowMultiple && openDescendantsWithoutActiveChildren.length === 0}>
                    <AddButton rootConcept={concept} numberOfExisting={activeLeafNodesByParent.length}/>
                </If>
            </div>
        );
    },
});

/**
 * We only want to show an <OpenInactiveConcept> if there is an open item without any active items in it.
 * This will return a flat array of any such concepts.
 * @param  {Concept} concept [description]
 * @return {Array} A nested array of open concepts.
 */
function getOpenDescendantsWithoutActiveChildren(concept) {
    const openDescendants = getOpenDescendants(concept);
    const flattenedOpenDescendants = flattenNested(openDescendants);
    return flattenedOpenDescendants.filter(hasNoActiveChildren);
}

/**
 * Returns a nested array of the open descendants of this concept (including itself).
 * If an open concept itself has open descendants, they are ignored.
 * @param  {Concept} concept [description]
 * @return {Array} A nested array of open concepts.
 */
function getOpenDescendants(concept) {
    if (concept.isOpen) {
        return [concept];
    }
    if (!concept.items) {
        return [];
    }
    return concept.items.map(child => getOpenDescendants(child));
}

/**
 * @param  {Concept} concept.
 * @return {Boolean} Does this concept have no active children?
 */
function hasNoActiveChildren(concept) {
    return !concept.items || concept.items.every(child => !child.isActive);
}

/**
 * Returns an array which groups all the nodes with the same parent id into separate sub-arrays.
 * @param  {Object[]} nodes An array of objects with a 'parent' property.
 * @param  {groupByParentId~idFunction} idFunction A function which gets the id of a parent.
 * @return {Object[]} An array of objects with keys parent, children.
 * @private
 */
function groupByParentId(nodes, idFunction) {
    const results = {};
    nodes.forEach(node => {
        const id = idFunction(node.parent);
        if (!results[id]) {
            results[id] = {parent: node.parent, children: []};
        }
        results[id].children.push(node);
    });
    return Object.keys(results).map(key => results[key]);
}

/**
* Function that is called to find the id of a parent.
* Eg. parent => parent.id.
* @callback groupByParentId~idFunction
* @param  {Object} parent A parent.
* @return {String} The parent id.
*/

const AddButton = createReactClass({
    displayName: 'AddButton',
    mixins: [ObserveModelMixin],

    propTypes: {
        rootConcept: PropTypes.object.isRequired,
        numberOfExisting: PropTypes.number
    },

    addNew() {
        this.props.rootConcept.closeDescendants();
        this.props.rootConcept.isOpen = true;
    },

    render() {
        const addText = (this.props.numberOfExisting > 0) ? ADD_MORE_TEXT : ADD_FIRST_TEXT;
        return (
            <div className={Styles.section}>
                <button onClick={this.addNew} className={Styles.btnAddNew}>
                    <Icon glyph={Icon.GLYPHS.add}/>
                    <span className={Styles.text}>{addText}</span>
                </button>
            </div>
        );
    },
});

module.exports = SummaryConcept;

