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, useEffect, useRef } from "react";
import { useCombobox } from "downshift";
import * as FontAwesome from "react-fontawesome";
import { FlexBox } from "../flex";
import { formElementSizeClassName, FormElementRequirement } from "../utils/Forms";
import "./Select2.less";
export var cssClass = {
    CONTAINER: "Select2--container",
    INFO_ROW: "Select2--infoRow",
    INFO_ROW_LABEL_HIDDEN: "Select2--infoRow--labelHidden",
    INFO_REQUIREMENT: "Select2--infoRequirement",
    LABEL: "Select2--label",
    LABEL_HIDDEN: "Select2--label--hidden",
    SELECT_CONTAINER: "Select2--selectContainer",
    SELECT_CONTAINER_FOCUSED: "Select2--selectContainer--focused",
    SELECT_CONTAINER_ERROR: "Select2--selectContainer--error",
    SELECT_CONTAINER_DISABLED: "Select2--selectContainer--disabled",
    INPUT: "Select2--input",
    TRAILING_ELEMENT: "Select2--trailingElement",
    ERROR_ICON: "Select2--errorIcon",
    BUTTON_RESET: "Select2--button--reset",
    CLEAR_BUTTON: "Select2--clearButton",
    CARET_BUTTON: "Select2--caretButton",
    MENU_RESET: "Select2--options--reset",
    MENU_OPEN: "Select2--options--open",
    MENU_OPTION: "Select2--option",
    MENU_OPTION_HIGHLIGHTED: "Select2--option--highlighted",
    NO_OPTIONS_FOUND: "Select2--notFound",
};
/*
  Basic single-item-select drop down component
*/
var Select2 = function (_a) {
    var className = _a.className, name = _a.name, label = _a.label, hideLabel = _a.hideLabel, initialOptions = _a.options, clearable = _a.clearable, creatable = _a.creatable, requirement = _a.requirement, initialIsInError = _a.initialIsInError, value = _a.value, onChange = _a.onChange, size = _a.size, placeholder = _a.placeholder;
    var _b = useState(null), createdOption = _b[0], setCreatedOption = _b[1];
    var options = createdOption ? __spreadArrays(initialOptions, [createdOption]) : initialOptions;
    var optionValues = new Set(options.map(function (o) { return o.value; }));
    var _c = useState(initialOptions), selectableOptions = _c[0], setSelectableOptions = _c[1];
    useEffect(function () {
        // We need this effect in order to get the state to update on prop change
        // in re-renders, otherwise options may be outdated
        setSelectableOptions(initialOptions);
    }, [initialOptions]);
    var _d = useState(false), isCreating = _d[0], setIsCreating = _d[1];
    // handle initial error state via focus state since
    // hooks can't combine the downshift state well with component hook state
    var _e = useState(false), hasBeenFocused = _e[0], setHasBeenFocused = _e[1];
    useEffect(function () {
        setHasBeenFocused(false);
    }, [initialIsInError]);
    var _f = useCombobox({
        items: selectableOptions,
        itemToString: function (o) { return (o ? o.label : ""); },
        selectedItem: options.find(function (o) { return o.value === value; }) || null,
        stateReducer: function (state, actionAndChanges) {
            var changes = actionAndChanges.changes, type = actionAndChanges.type;
            switch (type) {
                case useCombobox.stateChangeTypes.InputKeyDownEscape:
                    return __assign(__assign({}, changes), { selectedItem: null, inputValue: "", isOpen: true });
                case useCombobox.stateChangeTypes.InputBlur: {
                    // reset any text that has been entered
                    return __assign(__assign({}, changes), { selectedItem: selectedItem, inputValue: selectedItem ? selectedItem.label : "" });
                }
                default:
                    break;
            }
            return changes;
        },
        onIsOpenChange: function (changes) {
            var type = changes.type;
            switch (type) {
                // if we're opening the drop down for the first time
                // show everything even if something had already been selected previously
                case useCombobox.stateChangeTypes.FunctionOpenMenu:
                    setSelectableOptions(options);
                    break;
                default:
                    break;
            }
            return;
        },
        onInputValueChange: function (change) {
            var inputLowerCase = change.inputValue.toLowerCase();
            var filteredOptions = options.filter(function (o) { return o.label.toLowerCase().includes(inputLowerCase); });
            if (isCreating && filteredOptions.length > 0) {
                setIsCreating(false);
            }
            setSelectableOptions(filteredOptions);
        },
        onSelectedItemChange: function (item) {
            if (creatable) {
                if (optionValues.has(item.selectedItem.value)) {
                    setSelectableOptions(initialOptions);
                    setCreatedOption(null);
                }
                else {
                    setSelectableOptions(__spreadArrays(initialOptions, [item.selectedItem]));
                    setCreatedOption(item.selectedItem);
                    setIsCreating(false);
                }
            }
            if (onChange) {
                onChange(item.selectedItem ? item.selectedItem.value : "");
            }
        },
    }), isOpen = _f.isOpen, getToggleButtonProps = _f.getToggleButtonProps, getLabelProps = _f.getLabelProps, getMenuProps = _f.getMenuProps, getInputProps = _f.getInputProps, getComboboxProps = _f.getComboboxProps, highlightedIndex = _f.highlightedIndex, getItemProps = _f.getItemProps, openMenu = _f.openMenu, selectedItem = _f.selectedItem;
    var inputVal = getInputProps().value;
    useEffect(function () {
        if (creatable && selectableOptions.length === 0 && inputVal.length > 0) {
            setIsCreating(true);
            setSelectableOptions([{ label: "" + inputVal, value: inputVal }]);
        }
    }, [creatable, selectableOptions, setIsCreating, setSelectableOptions, inputVal]);
    var isInError = requirement === FormElementRequirement.REQUIRED &&
        (hasBeenFocused ? !selectedItem : initialIsInError);
    var id = name;
    var inputRef = useRef();
    return (React.createElement("div", { className: classNames(cssClass.CONTAINER, formElementSizeClassName(size), className) },
        React.createElement("div", { className: classNames(cssClass.INFO_ROW, hideLabel && cssClass.INFO_ROW_LABEL_HIDDEN) },
            React.createElement("label", __assign({ className: classNames(cssClass.LABEL, hideLabel && cssClass.LABEL_HIDDEN) }, getLabelProps()), label),
            requirement && (React.createElement("label", __assign({ className: cssClass.INFO_REQUIREMENT, "aria-live": "polite" }, getLabelProps()), requirement))),
        React.createElement("div", __assign({ className: classNames(cssClass.SELECT_CONTAINER, isOpen && cssClass.SELECT_CONTAINER_FOCUSED, isInError && cssClass.SELECT_CONTAINER_ERROR, requirement === FormElementRequirement.DISABLED && cssClass.SELECT_CONTAINER_DISABLED) }, getComboboxProps({
            onClick: function (e) {
                if (requirement === FormElementRequirement.DISABLED) {
                    e.stopPropagation();
                    return;
                }
                if (!isOpen) {
                    openMenu();
                    if (inputRef.current) {
                        inputRef.current.select();
                    }
                }
            },
        })),
            React.createElement("input", __assign({ id: id, name: id, placeholder: placeholder, className: cssClass.INPUT }, getInputProps({
                ref: inputRef,
                onFocus: function () { return setHasBeenFocused(true); },
                disabled: requirement === FormElementRequirement.DISABLED,
            }))),
            isInError && (React.createElement("div", { className: classNames(cssClass.TRAILING_ELEMENT, cssClass.ERROR_ICON) },
                React.createElement(FontAwesome, { name: "exclamation-circle" }))),
            clearable && selectedItem && (React.createElement(FlexBox, { justify: "center", alignItems: "center" },
                React.createElement("button", { className: classNames(cssClass.BUTTON_RESET, cssClass.TRAILING_ELEMENT, cssClass.CLEAR_BUTTON), "aria-label": "clear", onClick: function (e) {
                        e.stopPropagation();
                        if (requirement === FormElementRequirement.DISABLED) {
                            return;
                        }
                        onChange(null);
                        openMenu();
                        if (inputRef.current) {
                            inputRef.current.select();
                        }
                    } }, "\u2715"))),
            React.createElement("button", __assign({ className: classNames(cssClass.BUTTON_RESET, cssClass.TRAILING_ELEMENT, cssClass.CARET_BUTTON) }, getToggleButtonProps({ disabled: requirement === FormElementRequirement.DISABLED })),
                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 ? (selectableOptions.map(function (o, i) { 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 })), isCreating ? "Add \"" + o.label + "\"" : o.customLabel || o.label)); })) : (React.createElement("li", { className: classNames(cssClass.NO_OPTIONS_FOUND) }, "No options"))))));
};
export default Select2;
