var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
import * as classNames from "classnames";
import * as React from "react";
import { useState, useRef } from "react";
import { useCombobox, useMultipleSelection } from "downshift";
import * as FontAwesome from "react-fontawesome";
import Label from "../Label/Label";
import { formElementSizeClassName } from "../utils/Forms";
import "./MultiSelect.less";
// export for testing
export var ADD_NEW_ITEM_KEY = "MultiSelect--addNewItem";
export var cssClass = {
    CONTAINER: "MultiSelect--container",
    LABEL: "MultiSelect--label",
    LABEL_HIDDEN: "MultiSelect--label--hidden",
    SELECT_CONTAINER: "MultiSelect--selectContainer",
    SELECT_CONTAINER_FOCUSED: "MultiSelect--selectContainer--focused",
    SELECTED_ITEMS_CONTAINER: "MultiSelect--selectedItemsContainer",
    SELECTED_ITEM_WRAPPER: "MultiSelect--selectedItemWrapper",
    SELECTED_ITEM_CONTAINER: "MultiSelect--selectedItemContainer",
    SELECTED_ITEM_BUTTON: "MultiSelect--selectedItemButton",
    INPUT: "MultiSelect--input",
    CARET_BUTTON: "MultiSelect--caretButton",
    MENU_RESET: "MultiSelect--options--reset",
    MENU_OPEN: "MultiSelect--options--open",
    MENU_OPTION: "MultiSelect--option",
    MENU_OPTION_HIGHLIGHTED: "MultiSelect--option--highlighted",
    NO_OPTIONS_FOUND: "MultiSelect--notFound",
};
// export for testing
export function getSelectableOptions(options, selectedValues, inputValue, creatable, caseSensitive) {
    var selectedValuesSet = new Set(selectedValues);
    var inputCase = inputValue.toLocaleLowerCase();
    // if case sensitive, don't lowercase input for match evaluation
    if (caseSensitive) {
        inputCase = inputValue;
    }
    var hasExactMatch = false;
    var selectableOptions = options.filter(function (o) {
        var optionLowerCase = o.label.toLocaleLowerCase();
        // if case sensitive, don't lowercase label for match evaluation
        if (caseSensitive) {
            optionLowerCase = o.label;
        }
        // small performance optimization to process exact match within the same iterator
        if (optionLowerCase === inputCase) {
            hasExactMatch = true;
        }
        return (!selectedValuesSet.has(o.value) && (inputValue === "" || optionLowerCase.includes(inputCase)));
    });
    var creatableOption = [];
    if (creatable && !!inputValue && !hasExactMatch) {
        // add a dummy "add item X" placeholder
        // it will be special-case rendered and handled
        creatableOption.push({ value: ADD_NEW_ITEM_KEY });
    }
    return __spreadArrays(creatableOption, selectableOptions);
}
// pulling out English string literals to eventually be i18n friendly
// when i18n is prioritized for these components
var i18nStrings = {
    TOGGLE_BUTTON_LABEL: "toggle dropdown options",
};
/*
  Multi-item select drop down component
*/
var MultiSelect = function (_a) {
    var className = _a.className, name = _a.name, label = _a.label, hideLabel = _a.hideLabel, placeholder = _a.placeholder, 
    // we will manage the options in state since options are creatable
    initialOptions = _a.options, values = _a.values, creatable = _a.creatable, allowDuplicates = _a.allowDuplicates, caseSensitive = _a.caseSensitive, onChange = _a.onChange, size = _a.size;
    var _b = useState(initialOptions), options = _b[0], setOptions = _b[1];
    var _c = useState(""), inputValue = _c[0], setInputValue = _c[1];
    var _d = useMultipleSelection({
        itemToString: function (o) { return (o ? o.value : ""); },
        selectedItems: (values || []).map(function (v) { return options.find(function (o) { return o.value === v; }); }).filter(function (v) { return !!v; }),
        onSelectedItemsChange: function (change) {
            if (onChange) {
                onChange(change.selectedItems.map(function (o) { return o.value; }));
            }
        },
    }), getSelectedItemProps = _d.getSelectedItemProps, getDropdownProps = _d.getDropdownProps, selectedItems = _d.selectedItems;
    var selectableOptions = getSelectableOptions(options, allowDuplicates ? [] : values, inputValue, creatable, caseSensitive);
    var _e = useCombobox({
        inputValue: inputValue,
        // useMultipleSelection will separately take care of all "selected item" interactions
        // without this line, interaction with adding/removing will be buggy
        selectedItem: null,
        items: selectableOptions,
        // taken from https://www.downshift-js.com/use-multiple-selection
        stateReducer: function (state, actionAndChanges) {
            var changes = actionAndChanges.changes, type = actionAndChanges.type;
            switch (type) {
                case useCombobox.stateChangeTypes.InputKeyDownEnter:
                case useCombobox.stateChangeTypes.ItemClick:
                    return __assign(__assign({}, changes), { isOpen: true, highlightedIndex: state.highlightedIndex });
                default:
                    break;
            }
            return changes;
        },
        // default built-in changes generated here
        // https://github.com/downshift-js/downshift/blob/a13308910282c44ea2ebdcbc8a6dfee901df48a2/src/hooks/useCombobox/reducer.js
        // taken from https://www.downshift-js.com/use-multiple-selection
        onStateChange: function (changes) {
            var value = changes.inputValue, type = changes.type, selectedItem = changes.selectedItem;
            switch (type) {
                case useCombobox.stateChangeTypes.InputChange:
                    setInputValue(value);
                    break;
                case useCombobox.stateChangeTypes.InputKeyDownEnter:
                case useCombobox.stateChangeTypes.ItemClick:
                    if (selectedItem) {
                        setInputValue("");
                        var newOption = selectedItem;
                        if (selectedItem.value === ADD_NEW_ITEM_KEY) {
                            newOption = { value: inputValue, label: inputValue };
                            setOptions(__spreadArrays(options, [newOption]));
                        }
                        onChange(__spreadArrays(selectedItems, [newOption]).map(function (o) { return o.value; }));
                    }
                    break;
                case useCombobox.stateChangeTypes.InputBlur:
                    setInputValue("");
                    break;
                default:
                    break;
            }
        },
    }), isOpen = _e.isOpen, getToggleButtonProps = _e.getToggleButtonProps, getLabelProps = _e.getLabelProps, getMenuProps = _e.getMenuProps, getInputProps = _e.getInputProps, getComboboxProps = _e.getComboboxProps, getItemProps = _e.getItemProps, highlightedIndex = _e.highlightedIndex, openMenu = _e.openMenu;
    var id = name;
    var inputRef = useRef();
    return (React.createElement("div", { className: classNames(cssClass.CONTAINER, formElementSizeClassName(size), className) },
        React.createElement("label", __assign({ className: classNames(cssClass.LABEL, hideLabel && cssClass.LABEL_HIDDEN) }, getLabelProps()), label),
        React.createElement("div", __assign({ className: classNames(cssClass.SELECT_CONTAINER, isOpen && cssClass.SELECT_CONTAINER_FOCUSED) }, getComboboxProps({
            onClick: function (e) {
                if (!isOpen) {
                    openMenu();
                }
                if (inputRef.current) {
                    inputRef.current.focus();
                }
            },
        })),
            React.createElement("div", { className: cssClass.SELECTED_ITEMS_CONTAINER },
                selectedItems.map(function (item, i) { return (
                // need this wrapper element to support downshift accessibility
                React.createElement("span", __assign({ key: "" + item.value + i, className: cssClass.SELECTED_ITEM_WRAPPER }, getSelectedItemProps({ selectedItem: item, index: i })),
                    React.createElement(Label, { className: cssClass.SELECTED_ITEM_CONTAINER },
                        item.customLabel || item.label,
                        React.createElement("span", { className: cssClass.SELECTED_ITEM_BUTTON, onClick: function (e) {
                                e.stopPropagation();
                                onChange(__spreadArrays(selectedItems.slice(0, i), selectedItems.slice(i + 1)).map(function (o) { return o.value; }));
                                if (inputRef.current) {
                                    inputRef.current.select();
                                }
                            } }, "\u2715")))); }),
                React.createElement("input", __assign({ id: id, name: id, className: cssClass.INPUT, placeholder: placeholder }, getInputProps(getDropdownProps({
                    ref: inputRef,
                    // prevents the user from navigating to the selected items when text is present
                    preventKeyAction: inputValue !== "",
                    onFocus: function (e) {
                        if (!isOpen) {
                            openMenu();
                        }
                    },
                }))))),
            React.createElement("button", __assign({ "aria-label": i18nStrings.TOGGLE_BUTTON_LABEL, className: cssClass.CARET_BUTTON }, getToggleButtonProps()),
                React.createElement(FontAwesome, { name: isOpen ? "caret-up" : "caret-down" }))),
        React.createElement("ul", __assign({ className: classNames(cssClass.MENU_RESET, isOpen && cssClass.MENU_OPEN) }, getMenuProps()), isOpen &&
            (selectableOptions.length === 0 ? (React.createElement("li", { className: classNames(cssClass.NO_OPTIONS_FOUND) }, "No options")) : (selectableOptions.map(function (o, i) {
                var isAddNewItemOption = o.value === ADD_NEW_ITEM_KEY;
                return (React.createElement("li", __assign({ className: classNames(cssClass.MENU_OPTION, i === highlightedIndex && cssClass.MENU_OPTION_HIGHLIGHTED), key: "" + o.value + i }, getItemProps({ item: o, index: i })), isAddNewItemOption ? "Add \"" + inputValue + "\"" : o.customLabel || o.label));
            }))))));
};
export default MultiSelect;
