'use strict';

import {findDOMNode} from 'react-dom';
import {Store} from '../../data/Store';
import {FormGroup} from './FormGroup';
import {FormControl, FormControlDefaultProps, FormControlProps} from './FormControl';
import {Checkbox} from './Checkbox';
import {Filter} from '../Filter';
import {Icon} from '../Icon';
import {Tooltip} from '../Tooltip';
import React from 'react';
import PropTypes from 'prop-types';
import {Dropdown, OverlayTrigger} from 'react-bootstrap';
import _ from 'lodash';
import classnames from 'classnames';
import {highlight} from '../list/utils';
import {translate} from '../../../utils';
import {FocusHelper} from '../../utils/FocusHelper';
import {KeyNames} from '../../../utils/constants';
import {DropdownProps} from '../Dropdown';
import lang from '../../../lang/common/index-en_US';

const FC = FormControl;
const doNothing = () => {};

export type MultiSelectProps = DropdownProps & {
    id?: string | number;
    isFilterable?: boolean;
    searchText?: string;
    noResultsText?: string;
    label?: string;
    overlayProps?: object;
    value?: any;
    testAutomationId?: string;
    className?: string;
    block?: boolean;
    stretch?: boolean;
    listHeader?: any;
    listItemsLimit?: number;
    labelItemsLimit?: number;
    onChange?: (value: any) => void;
    showMoreCountTextTemplate?: string;
    toggleAriaLabel?: string;
    selectedValuesPrefix?: string;
    applyLabelPrefixForSingleMode?: boolean;
    applyLabelPrefixForMultiMode?: boolean;
    ariaLabelledById?: string;
    behaveAsFlat: boolean;
} & FormControlProps &
    Partial<MultiSelectDefaultProps> &
    React.HTMLProps<MultiSelect>;

export type MultiSelectDefaultProps = {
    isFilterable: boolean;
    labelItemsLimit: number;
    testAutomationId: string;
    searchText: string;
    noResultsText: string;
    disabled: boolean;
    showMoreCountTextTemplate: string;
    listItemsLimit: number;
    isSingleSelect: any;
    stretchDropdownMenu: any;
} & FormControlDefaultProps;

export type MultiSelectState = {
    filterText: string;
    filterRegExp: RegExp | null;
    filterCallback: (label: string, value: any) => boolean;
    isOpen: boolean;
    isMoreShowed: boolean;
    hasFilterFocus: boolean;
};

// declare namespace MultiSelectModule {
//     class MultiSelectOption extends React.Component<any> {}
//     class MultiSelectMasterOption extends React.Component<any> {}
//     class MultiSelectOptionGroup extends React.Component<any> {}
//     class MultiSelectField extends React.Component<any> {}
// }

class MultiSelectField extends FormGroup<any> {
    static propTypes: any = {
        ...FormGroup.propTypes
    };

    static defaultProps = {
        ...FormGroup.defaultProps,
        testAutomationId: 'multiSelect'
    };

    renderInput() {
        let {children, innerLabel, ...props} = this.getPropsFromContext();
        const MultiSelectTS: any = MultiSelect;
        return (
            <MultiSelectTS {...props} label={innerLabel} ref="control" hasFormGroupContainer>
                {children}
            </MultiSelectTS>
        );
    }
}

let multiSelectInstanceCounter = 0;

class MultiSelectOption extends React.Component<any, any> {
    static contextTypes = {
        multiSelect: PropTypes.object,
        multiSelectGroup: PropTypes.object,
        getOptionIdPrefix: PropTypes.func,
        setAriaActivedescendant: PropTypes.func
    };

    static childContextTypes = {
        getOptionIdPrefix: PropTypes.func
    };

    static propTypes = {
        className: PropTypes.string,
        disabled: PropTypes.bool,
        groupMaster: PropTypes.bool,
        icon: PropTypes.string,
        label: PropTypes.string.isRequired,
        labelClassName: PropTypes.string,
        onKeyDown: PropTypes.func,
        onSelect: PropTypes.func,
        testAutomationId: PropTypes.string,
        value: PropTypes.any.isRequired,
        ariaPosition: PropTypes.number,
        ariaLevel: PropTypes.number,
        ariaSetSize: PropTypes.number,
        hint: PropTypes.string,
        customLabel: PropTypes.any,
        tagRenderer: PropTypes.func,
        description: PropTypes.string,
        secondLine: PropTypes.string
    };

    static defaultProps = {
        className: null,
        disabled: false,
        groupMaster: false,
        icon: '',
        labelClassName: '',
        onKeyDown: doNothing,
        onSelect: doNothing,
        testAutomationId: null,
        ariaPosition: null,
        ariaLevel: null,
        ariaSetSize: null,
        hint: null,
        customLabel: null,
        tagRenderer: null,
        description: null,
        secondLine: null,
        overlayProps: {}
    };

    static compareOptionValue(source) {
        if (_.isObject(source)) {
            return _.matches(source);
        }
        return function(value) {
            return value === source;
        };
    }

    constructor(props, context) {
        super(props, context);

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

    getChildContext() {
        return {
            getOptionIdPrefix: () => this.getId()
        };
    }

    handleKeyDown(event) {
        switch (event.keyCode) {
            case 13: // enter
            case 32: // space
                (this.refs.control as any).click();
                event.preventDefault();
                break;
            default:
                this.props.onKeyDown(event);
        }
    }

    getId() {
        if (!_.isFunction(this.context.getOptionIdPrefix)) {
            return null;
        }

        return `${this.context.getOptionIdPrefix()}_${
            this.props.groupMaster ? 'masterOption' : 'option'
        }_${this.props.ariaPosition}`;
    }

    finalRender(props, content) {
        if (_.isFunction(this.props.tagRenderer)) {
            return this.props.tagRenderer(props, content);
        }
        return content;
    }

    render() {
        const {hint, description, secondLine, overlayProps} = this.props;

        const {multiSelect: {behaveAsFlat}} = this.context;

        let values = this.context.multiSelect.values;
        let filterCallback = this.context.multiSelect.filterCallback;

        let isSelected: any = false;
        let isMaster = this.props.groupMaster;

        let isOptionDisabled =
            !!(
                isMaster &&
                this.context.multiSelect.isSingleSelectMode &&
                this.props.value === undefined
            ) || this.props.disabled;

        let optionValues: any = [];

        let labelTitle = !description ? this.props.label : `${this.props.label} - ${description}`;

        if (behaveAsFlat) {
            isSelected = values.some(MultiSelectOption.compareOptionValue(this.props.value));
        } else if (!isMaster) {
            optionValues = [this.props.value];

            let index = _.findIndex(values, MultiSelectOption.compareOptionValue(this.props.value));
            isSelected = index > -1;

            // Skip not matched options
            if (!filterCallback(this.props.label, this.props.value)) {
                return null;
            }
        } else {
            optionValues = this.context.multiSelectGroup.values;

            let selectedValuesCount = 0;
            let groupFilteredLabels = this.context.multiSelectGroup.filteredLabels;

            optionValues.forEach(value => {
                let index = _.findIndex(values, MultiSelectOption.compareOptionValue(value));
                if (index > -1) {
                    selectedValuesCount++;
                }
            });

            if (selectedValuesCount > 0) {
                isSelected = selectedValuesCount === optionValues.length ? true : null;
            }

            // Skip not matched groups
            if (
                !filterCallback(this.props.label, this.props.value) &&
                !groupFilteredLabels.length
            ) {
                return null;
            }
        }

        const highlightedLabelText =
            this.props.customLabel ||
            highlight(this.context.multiSelect.filterRegExp, this.props.label);
        let icon = this.props.icon;
        let content;

        const label = (
            <span className="multi-select__item-text-container">
                {highlightedLabelText}
                {description !== null ? (
                    <span className="multi-select__item-hint select-description">
                        {' '}
                        - {description}
                    </span>
                ) : null}
            </span>
        );

        if (this.context.multiSelect.isSingleSelectMode) {
            isSelected =
                this.props.value !== undefined &&
                MultiSelectOption.compareOptionValue(this.props.value)(values[0]);

            let onClick = event => {
                if (this.props.value !== undefined) {
                    this.context.multiSelect.setValues([this.props.value]);
                }
                this.context.multiSelect.close();
            };

            let labelProps: any = {
                className: classnames('option-label', this.props.labelClassName, {
                    'selected-option': isSelected
                }),
                ref: 'control'
            };
            if (!isOptionDisabled) {
                labelProps.onClick = onClick;
            }
            const secondLineElement = secondLine ? (
                <div className="second-line text-overflow" title={secondLine}>
                    {secondLine}
                </div>
            ) : null;
            content = (
                <span {...labelProps}>
                    {isSelected ? (
                        <Icon aria-hidden="true" name="checkbox-selected-readonly" />
                    ) : null}
                    {label}
                    {secondLineElement}
                    <div className="icon-wrapper" aria-hidden="true">
                        {icon ? <Icon name={icon} /> : null}
                    </div>
                </span>
            );
        } else {
            content = (
                <Checkbox
                    name={undefined as any}
                    ref="control"
                    label={label as any}
                    labelClassName={this.props.labelClassName}
                    checked={isSelected}
                    aria-hidden={true}
                    aria-checked={isSelected === null ? 'mixed' : isSelected}
                    disabled={isOptionDisabled}
                    onChange={() => {
                        const isSelected = (this.refs.control as any).getValue();
                        const next = behaveAsFlat ? [this.props.value] : optionValues;

                        const newValues = isSelected
                            ? values.concat(next)
                            : values.filter(source => {
                                  const compare = MultiSelectOption.compareOptionValue(source);

                                  return next.every(value => !compare(value));
                              });

                        this.context.multiSelect.setValues(newValues);
                    }}
                />
            );
        }

        const optionId = this.getId();

        let liProps: any = {
            id: optionId,
            'data-test-automation-id':
                this.props.testAutomationId || 'option' + this.context.multiSelect.uid(),
            className: classnames(
                `multi-select-option multi-select-option-level-${this.props.ariaLevel}`,
                this.props.className,
                {
                    'master-option': isMaster,
                    'option-disabled': isOptionDisabled
                }
            ),
            role: 'treeitem',
            'aria-level': this.props.ariaLevel,
            'aria-setsize': this.props.ariaSetSize,
            'aria-posinset': this.props.ariaPosition
        };

        if (!isOptionDisabled) {
            liProps.tabIndex = '-1';
            liProps.onKeyDown = this.handleKeyDown;
            liProps.onSelect = this.props.onSelect;
            liProps['aria-selected'] = isSelected === true;
            liProps.onFocus = () => {
                if (_.isFunction(this.context.setAriaActivedescendant)) {
                    this.context.setAriaActivedescendant(optionId);
                }
            };
        }

        const finalContent = hint ? (
            <OverlayTrigger overlay={<Tooltip>{hint}</Tooltip>} {...overlayProps}>
                {content}
            </OverlayTrigger>
        ) : (
            this.finalRender(liProps, content)
        );

        return (
            <li {...liProps} title={labelTitle}>
                {finalContent}
            </li>
        );
    }
}

class MultiSelectMasterOptionWrapper extends React.Component<any, any> {
    static propTypes: any = {
        ...MultiSelectOption.propTypes,
        testAutomationId: PropTypes.string,
        groupLabelId: PropTypes.string.isRequired
    };

    static defaultProps = {
        ...MultiSelectOption.defaultProps,
        testAutomationId: null
    };

    static contextTypes = {
        multiSelect: PropTypes.object
    };

    getTestAutomationId() {
        const {testAutomationId} = this.props;
        const {multiSelect} = this.context;

        if (testAutomationId) {
            return testAutomationId;
        }

        return `optionMaster${multiSelect.uid()}`;
    }

    tagRenderer = (props, content) => {
        return <div id={this.props.groupLabelId}>{content}</div>;
    };

    render() {
        return (
            <MultiSelectOption
                {...this.props}
                testAutomationId={this.getTestAutomationId()}
                tagRenderer={this.tagRenderer}
                groupMaster
            />
        );
    }
}

class MultiSelectMasterOption extends React.Component {
    static propTypes: any = {
        ...MultiSelectOption.propTypes
    };

    render() {
        return null;
    }
}

function getChildrenValues(children) {
    return _.flatten(
        React.Children.toArray(children)
            .filter((child: any) => {
                return (
                    (child.type === MultiSelectOption && !child.props.groupMaster) ||
                    child.type === MultiSelectOptionGroup
                );
            })
            .map((child: any) => {
                if (child.type === MultiSelectOption) {
                    return child.props.value;
                }
                return getChildrenValues(child.props.children);
            })
    );
}

function hasValueChild(children) {
    const items: any = React.Children.toArray(children);
    const hasValue = items.some((o: any) => o && o.props && o.props.value !== undefined);

    if (hasValue) {
        return true;
    }

    for (let child of items) {
        if (child && child.props && child.props.children && hasValueChild(child.props.children)) {
            return true;
        }
    }

    return false;
}

function getChildrenLabels(children, filterCallback) {
    return _.flatten(
        React.Children.toArray(children)
            .filter((child: any): any => {
                if (child.type === MultiSelectOption || child.type === MultiSelectMasterOption) {
                    if (filterCallback && !filterCallback(child.props.label, child.props.value)) {
                        return false;
                    }

                    return true;
                } else if (child.type === MultiSelectOptionGroup) {
                    return true;
                }
            })
            .map((child: any) => {
                if (child.type === MultiSelectOption || child.type === MultiSelectMasterOption) {
                    return child.props.label;
                }
                return getChildrenLabels(child.props.children, filterCallback);
            })
    );
}

function getGroupValueOptionSize(children) {
    if (!Array.isArray(children)) {
        children = React.Children.toArray(children);
    }
    return children.filter(o => !isMasterOption(o)).length;
}

function isMasterOption(node) {
    return node && node.type === MultiSelectMasterOption;
}

const TreeItemAriaProps = ['aria-selected', 'aria-level', 'aria-setsize', 'aria-posinset', 'role'];

class MultiSelectOptionGroup extends React.Component<any> {
    static contextTypes = {
        multiSelect: PropTypes.object,
        multiSelectGroup: PropTypes.object,
        getOptionIdPrefix: PropTypes.func
    };

    static childContextTypes = {
        multiSelectGroup: PropTypes.object,
        getOptionIdPrefix: PropTypes.func
    };

    static propTypes = {
        children: PropTypes.node,
        className: PropTypes.string,
        onKeyDown: PropTypes.func,
        onSelect: PropTypes.func,
        testAutomationId: PropTypes.string,
        ariaPosition: PropTypes.number,
        ariaLevel: PropTypes.number,
        ariaSetSize: PropTypes.number
    };

    static defaultProps = {
        children: null,
        className: null,
        onKeyDown: doNothing,
        onSelect: doNothing,
        testAutomationId: null,
        ariaPosition: 1,
        ariaLevel: 1,
        ariaSetSize: 1
    };

    getChildContext() {
        return {
            multiSelectGroup: {
                values: getChildrenValues(this.props.children),
                filteredLabels: getChildrenLabels(
                    this.props.children,
                    this.context.multiSelect.filterCallback
                )
            },
            getOptionIdPrefix: this.getOptionId
        };
    }

    getOptionId = () => {
        if (_.isFunction(this.context.getOptionIdPrefix)) {
            return `${this.context.getOptionIdPrefix()}_${this.props.ariaPosition}`;
        }
        return null;
    };

    getSiblingOptionSize(children) {
        return getGroupValueOptionSize(children);
    }

    getMasterOptionWrapperProps = props => {
        return _.omit(props, TreeItemAriaProps);
    };

    getGroupProps(props) {
        if (_.isEmpty(props)) {
            return null;
        }

        return _.pick(props, TreeItemAriaProps);
    }

    render() {
        const children = React.Children.toArray(this.props.children);
        const ariaSetSize = this.getSiblingOptionSize(children);

        let hasMaster = false;

        const items = children.map((child: any, index) => {
            let childProps = child.props || {};
            const isMasterNode = isMasterOption(child);
            if (isMasterNode) {
                hasMaster = true;
            }

            const ariaPosition = hasMaster ? index : index + 1;

            return React.cloneElement(
                child,
                {
                    ariaPosition: isMasterNode ? this.props.ariaPosition : ariaPosition,
                    ariaLevel: this.props.ariaLevel + (isMasterNode ? 0 : 1),
                    ariaSetSize: isMasterNode ? this.props.ariaSetSize : ariaSetSize,
                    onKeyDown: this.props.onKeyDown,
                    onSelect: this.props.onSelect
                },
                childProps.children
            );
        });

        const masterItem = _.find(items, isMasterOption);

        const groupLabelId = `${this.getOptionId()}_group_node`;

        const masterOptionNode = masterItem ? (
            <MultiSelectMasterOptionWrapper
                groupLabelId={groupLabelId}
                {...this.getMasterOptionWrapperProps(masterItem.props)}
            />
        ) : null;

        const valueItems = items.filter(node => !isMasterOption(node));
        const hasAvailableValue = hasValueChild(valueItems);

        const groupProps: any = {
            id: this.getOptionId(),
            'data-test-automation-id':
                this.props.testAutomationId || 'groupOption' + this.context.multiSelect.uid(),
            role: 'treeitem',
            'aria-expanded': true,
            'aria-selected': false,
            ...this.getGroupProps(masterItem && masterItem.props)
        };

        groupProps.className = classnames(groupProps.className, {
            'multi-select-has-children': !_.isEmpty(valueItems)
        });

        let ariaProps = {};
        if (hasAvailableValue && masterItem) {
            ariaProps = {'aria-labelledby': groupLabelId};
        }

        return (
            <div {...groupProps}>
                {masterOptionNode}
                <ul
                    className={classnames('multi-select-group', this.props.className)}
                    role={hasAvailableValue ? 'group' : ''}
                    {...ariaProps}
                >
                    {valueItems}
                </ul>
            </div>
        );
    }
}

const MultiSelectDefaultAutomationId = 'multiSelect';

export class MultiSelect extends FormControl<MultiSelectProps> {
    static propTypes: any = {
        ...FormControl.propTypes,
        id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        isFilterable: PropTypes.bool,
        searchText: PropTypes.string,
        noResultsText: PropTypes.string,
        label: PropTypes.string,
        value: PropTypes.any,
        testAutomationId: PropTypes.string,
        className: PropTypes.string,
        block: PropTypes.bool,
        stretch: PropTypes.bool,
        listHeader: PropTypes.any,
        listItemsLimit: PropTypes.number,
        labelItemsLimit: PropTypes.number,
        onChange: PropTypes.func,
        isSingleSelect: PropTypes.bool,
        showMoreCountTextTemplate: PropTypes.string,
        toggleAriaLabel: PropTypes.string,
        selectedValuesPrefix: PropTypes.string,
        applyLabelPrefixForSingleMode: PropTypes.bool,
        applyLabelPrefixForMultiMode: PropTypes.bool,
        ariaLabelledById: PropTypes.string,
        stretchDropdownMenu: PropTypes.bool,
        behaveAsFlat: PropTypes.bool
    };

    static childContextTypes = {
        multiSelect: PropTypes.object,
        level: PropTypes.number,
        getOptionIdPrefix: PropTypes.func,
        setAriaActivedescendant: PropTypes.func
    };

    static Option: typeof MultiSelectOption = MultiSelectOption;
    static MasterOption: typeof MultiSelectMasterOption = MultiSelectMasterOption;
    static Group: typeof MultiSelectOptionGroup = MultiSelectOptionGroup;
    static MultiSelectField: typeof MultiSelectField = MultiSelectField;

    // extends defaultProps from superclass
    static defaultProps = {
        ...FC.defaultProps,
        isFilterable: false,
        labelItemsLimit: 3,
        testAutomationId: MultiSelectDefaultAutomationId,
        get searchText() {
            return lang.SEARCH;
        },
        get noResultsText() {
            return lang.NO_RESULTS;
        },
        disabled: false,
        isSingleSelect: false,
        get showMoreCountTextTemplate() {
            return lang.SHOW_MORE_COUNT;
        },

        /** example string format: "{filterName} filter" */
        get toggleAriaLabel() {
            return lang.MULTI_SELECT_ARIA_LABEL;
        },

        /**  example string format: "selected {filterName}: {values}" */
        get selectedValuesPrefix() {
            return lang.MULTI_SELECT_SELECTED_TITLE_LABEL;
        },
        applyLabelPrefixForSingleMode: false,
        applyLabelPrefixForMultiMode: true,
        stretchDropdownMenu: false,
        behaveAsFlat: false
    };

    focusHelper = new FocusHelper();
    listItemsFilteredByLimit: any;
    listItemsFiltered: any;
    multiSelectEle: any;
    instanceId: any;
    uid: any;

    constructor(props, context) {
        super(props, context);

        this.onToggle = this.onToggle.bind(this);
        this.onKeyDown = this.onKeyDown.bind(this);
        this.showMore = this.showMore.bind(this);
        this.filterByLimit = this.filterByLimit.bind(this);
        this.filterByLabelAndLimit = this.filterByLabelAndLimit.bind(this);
        this.listItemsFilteredByLimit = [];
        this.listItemsFiltered = [];

        this.state = {
            filterText: '',
            filterRegExp: null,
            filterCallback: this.filterByLimit,
            isOpen: false,
            isMoreShowed: false,
            hasFilterFocus: false,
            ariaActivedescendant: null
        } as any;
        this.instanceId = ++multiSelectInstanceCounter;
    }

    componentDidMount() {
        this.multiSelectEle.addEventListener('keydown', event => {
            if (this.state.isOpen && event.key === KeyNames.Escape) {
                event.stopPropagation();
            }
        });
    }

    saveMultiSelectRef = ref => {
        this.multiSelectEle = findDOMNode(ref);
    };

    filterByLimit(label: string, value: any): boolean {
        if (!((this.props.listItemsLimit as any) > 0)) {
            return true;
        }
        if (this.state.isMoreShowed) {
            return true;
        }
        let validValue = typeof value !== 'undefined';

        // Calculate all visible options
        if (validValue && this.listItemsFiltered.indexOf(value) === -1) {
            this.listItemsFiltered.push(value);
        }
        if (this.listItemsFilteredByLimit.length < (this.props as any).listItemsLimit) {
            if (validValue && this.listItemsFilteredByLimit.indexOf(value) === -1) {
                this.listItemsFilteredByLimit.push(value);
            }
            return true;
        }
        return this.listItemsFilteredByLimit.indexOf(value) !== -1;
    }

    filterByLabelAndLimit(label, value): boolean {
        let filterRegExp = this.state.filterRegExp;
        if (filterRegExp === null) {
            return this.filterByLimit(label, value);
        }
        filterRegExp.lastIndex = 0;
        return filterRegExp.test(label) && this.filterByLimit(label, value);
    }

    focus = () => {
        this.focusHelper.focus();
    };

    outputConverter(e: any): any {
        return e;
    }

    getValue(): null {
        // FIXME [UIA-10000] Not implemented
        return null;
    }

    getChildContext() {
        let props: any = this.getPropsFromContext();

        return {
            multiSelect: {
                uid: () => {
                    return ++this.uid;
                },

                values: toArray(props.value),
                setValues: values => {
                    if (this.props.isSingleSelect) {
                        values = values[0];
                    }
                    props.onChange(values);
                },
                filterCallback: this.state.filterCallback,
                filterRegExp: this.state.filterRegExp,
                isSingleSelectMode: this.props.isSingleSelect,
                behaveAsFlat: this.props.behaveAsFlat,
                close: () => {
                    this.setState({isOpen: false});
                }
            },
            level: 0,
            getOptionIdPrefix: () => `select_${this.instanceId}`,
            setAriaActivedescendant: ariaActivedescendant => this.setState({ariaActivedescendant})
        };
    }

    onToggle(isOpen: boolean): void {
        this.uid = 0;

        if (!isOpen && this.state.hasFilterFocus) {
            isOpen = true;
        }

        this.setState({isOpen});
    }

    onKeyDown(event: React.KeyboardEvent<any>): void {
        switch (event.key) {
            case 'Escape':
            case 'Tab':
                this.setState({isOpen: false});
                break;
        }
    }

    setTextFilter(text: string): void {
        let filterRegExp = Store.searchStringToRegExp(text);
        this.listItemsFiltered = [];
        this.listItemsFilteredByLimit = [];

        this.setState({
            filterText: text,
            filterRegExp,
            filterCallback: this.filterByLabelAndLimit,
            isMoreShowed: false
        });
    }

    showMore(event: React.MouseEvent<any>): void {
        event.preventDefault();
        this.setState({
            isMoreShowed: true
        });
    }

    renderShowMore(): React.ReactNode {
        if (this.state.isMoreShowed) {
            return null;
        }
        let count = this.listItemsFiltered.length - this.listItemsFilteredByLimit.length;
        if (count <= 0) {
            return null;
        }
        return (
            <li role="menuitem">
                <a className="show-more" onClick={this.showMore}>
                    {translate(this.props.showMoreCountTextTemplate as any, {count})}
                </a>
            </li>
        );
    }

    /** @param {Array<String>} selectedItemLabels */
    getTitle = selectedItemLabels => {
        if (_.isEmpty(selectedItemLabels)) {
            return null;
        }

        const {label, selectedValuesPrefix} = this.props;

        const valueStr = selectedItemLabels.join(', ');

        if (!this.shouldApplyLabelPrefix()) {
            return valueStr;
        }

        return translate(selectedValuesPrefix as any, {
            filterName: label,
            values: valueStr
        });
    };

    getAriaLabel = () => {
        const {label, toggleAriaLabel} = this.props;

        if (!this.shouldApplyLabelPrefix()) {
            return label;
        }

        return translate(toggleAriaLabel as any, {filterName: label});
    };

    getAriaDescribedbyProp() {
        const {'aria-describedby': describedby} = this.props;

        return describedby ? {'aria-describedby': describedby} : {};
    }

    /** @param {Array<String>} selectedItems */
    getDisplayLabel = selectedItems => {
        const {label, labelItemsLimit} = this.props as any;

        if (_.isEmpty(selectedItems)) {
            return label;
        }

        let valueString = selectedItems.join(', ');

        if (selectedItems.length > labelItemsLimit) {
            valueString = selectedItems
                .slice(0, labelItemsLimit)
                .concat(['...'])
                .join(', ');
        }

        return this.shouldApplyLabelPrefix() ? `${label}: ${valueString}` : valueString;
    };

    shouldApplyLabelPrefix() {
        const {
            isSingleSelect,
            applyLabelPrefixForSingleMode,
            applyLabelPrefixForMultiMode
        } = this.props;

        return isSingleSelect ? applyLabelPrefixForSingleMode : applyLabelPrefixForMultiMode;
    }

    /** @returns {Array<String>}*/
    getSelectedItemLabels = () => {
        const {children} = this.props;
        let props = this.getPropsFromContext();
        let values = toArray(props.value);

        return getChildrenLabels(children, (label, value) => {
            return _.findIndex(values, MultiSelectOption.compareOptionValue(value)) > -1;
        });
    };

    render() {
        this.uid = 0;

        let {
            id,
            isFilterable,
            searchText,
            noResultsText,
            label,
            value,
            children,
            labelItemsLimit,
            testAutomationId,
            className,
            block,
            stretch,
            listHeader,
            listItemsLimit,
            showMoreCountTextTemplate,
            onChange,
            ariaLabelledById,
            toggleAriaLabel,
            stretchDropdownMenu,
            ...otherProps
        } = this.props;
        let filter: any = null;
        let isNoResults = false;
        let hasChildren = children && (!(children instanceof Array) || children.length);

        if (isFilterable && hasChildren) {
            filter = (
                <Filter
                    value={this.state.filterText}
                    testAutomationId="filter"
                    className="multi-select-filter"
                    ref="filter"
                    placeholder={searchText}
                    tabIndex={-1}
                    onFocus={event => {
                        if (!this.state.hasFilterFocus) {
                            (this.refs.filter as any).focus();
                            this.setState({hasFilterFocus: true});
                        }
                    }}
                    onBlur={event => this.setState({hasFilterFocus: false})}
                    onKeyDown={event => {
                        if (['ArrowUp', 'ArrowDown', 'Escape', 'Tab'].indexOf(event.key) === -1) {
                            event.stopPropagation();
                        }
                    }}
                    onChange={filterText => this.setTextFilter(filterText)}
                />
            );

            isNoResults = getChildrenLabels(children, this.state.filterCallback).length === 0;
        }

        let header: any = null;
        if (listHeader && filter) {
            header = (
                <li className="multi-select-list-header" role="none">
                    {listHeader}
                    {filter}
                </li>
            );
        } else if (listHeader) {
            header = (
                <li className="multi-select-list-header" role="none">
                    {listHeader}
                </li>
            );
        } else if (filter) {
            header = (
                <li className="multi-select-list-header" role="none">
                    {filter}
                </li>
            );
        }

        const selectedItemLabels = this.getSelectedItemLabels();
        const title = this.getTitle(selectedItemLabels);
        const displayLabel = this.getDisplayLabel(selectedItemLabels);

        id =
            id ||
            ((testAutomationId !== MultiSelectDefaultAutomationId
                ? testAutomationId
                : Date.now()) as any);

        const dropdownMenuClassName = stretchDropdownMenu ? 'stretch' : null;

        return (
            <Dropdown
                id={id}
                onToggle={this.onToggle}
                onKeyDown={this.onKeyDown}
                open={this.state.isOpen}
                ref={this.saveMultiSelectRef}
                data-test-automation-id={
                    this.props.hasFormGroupContainer ? undefined : testAutomationId
                }
                className={classnames('multi-select', className, {
                    'btn-block': stretch || block,
                    stretch
                })}
                {...this.getSafeProps(otherProps)}>
                <Dropdown.Toggle
                    ref={this.focusHelper.setEl}
                    data-test-automation-id="toggle"
                    aria-labelledby={ariaLabelledById}
                    noCaret
                    disabled={this.props.disabled}
                    aria-label={this.getAriaLabel()}
                    {...this.getAriaDescribedbyProp()}
                    title={title}
                    className={classnames({'btn-block': stretch || block})}>
                    <span className="icon-wrapper" aria-hidden="true">
                        <Icon name="chevron-down-small" />
                    </span>
                    <div className="label-wrapper">
                        <div className="label-wrapper-inner">
                            <div className="label-text-overflow">{displayLabel}</div>
                        </div>
                    </div>
                </Dropdown.Toggle>
                <Dropdown.Menu
                    className={dropdownMenuClassName as any}
                    data-test-automation-id="list">
                    {header}
                    {!hasChildren || isNoResults ? (
                        <li className="no-results" data-test-automation-id="noResult" role="none">
                            {noResultsText}
                        </li>
                    ) : (
                        <li className="multi-select-list-content" role="menuitem">
                            <ul
                                role="tree"
                                aria-multiselectable={!this.props.isSingleSelect}
                                aria-activedescendant={this.state.ariaActivedescendant}>
                                {appendAriaPropsForMultiSelectChildren(children)}
                            </ul>
                        </li>
                    )}
                    {hasChildren && !isNoResults ? this.renderShowMore() : null}
                </Dropdown.Menu>
            </Dropdown>
        );
    }
}

function appendAriaPropsForMultiSelectChildren(children) {
    const arr = _.flatten(toArray(children));
    return _.map(arr, (child, index) => {
        if (_.isEmpty(child)) {
            return child;
        }

        const childProps = child.props || {};

        return React.cloneElement(
            child,
            {
                ariaPosition: index + 1,
                ariaLevel: 1,
                ariaSetSize: arr.length
            },
            childProps.children
        );
    });
}

function toArray(value) {
    return _.isArray(value) ? value : [value];
}
