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 __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
import { Clear } from '@procore/core-icons/dist';
import React from 'react';
import { isEventSource } from '../../effects/ClickOutside';
import { Box } from '../../layouts/Box';
import { addSubcomponents } from '../../utils/addSubcomponents';
import { MenuImperative } from '../MenuImperative';
import { OverlayTrigger } from '../OverlayTrigger';
import { Spinner } from '../Spinner';
import { Token } from '../Token';
import { Typography } from '../Typography';
import { StyledMultiSelectArrow, StyledMultiSelectArrowContainer, StyledMultiSelectButton, StyledMultiSelectClearIcon, StyledMultiSelectMenu, StyledMultiSelectSearch, StyledMultiSelectSearchIcon, StyledMultiSelectSearchInput, StyledMultiSelectToken, StyledMultiSelectValues, } from './MultiSelect.styles';
var targetShowKeys = ['ArrowDown', 'Down'];
var targetHideKeys = ['Esc', 'Escape'];
var overlayHideKeys = targetHideKeys;
var emptyArray = [];
function noop() { }
function returnTrue() {
    return true;
}
function isAlphaNumeric(str) {
    return str.length === 1 && str.match(/[A-Za-z0-9 _.,!"'/$]*/i);
}
function setFocusTo(ref) {
    if (ref.current) {
        ref.current.focus();
    }
}
function isOption(obj) {
    return typeof obj === 'object' && obj.type === Option;
}
function defaultIsOptionDisabled() {
    return false;
}
function defaultGetId(item) {
    return item.id;
}
function defaultGetLabel(item) {
    return item.label;
}
function defaultGetGroup(item) {
    return item.groupId;
}
function defaultIsSelectable(obj) {
    typeof obj === 'object' && obj.type === Option;
}
function defaultOptionRenderer(item, _a) {
    var _b;
    var getId = _a.getId, getLabel = _a.getLabel, qa = _a.qa, value = _a.value, props = __rest(_a, ["getId", "getLabel", "qa", "value"]);
    return (React.createElement(Option, __assign({ key: getId(item), value: value || item, "data-qa": (_b = qa === null || qa === void 0 ? void 0 : qa.option) === null || _b === void 0 ? void 0 : _b.call(qa, item) }, props), getLabel(item)));
}
function defaultGroupHeaderRenderer(group) {
    return (React.createElement(MenuImperative.Group, { key: "group_".concat(group.id), clickable: false }, group.label));
}
function defaultTokenRenderer(_a) {
    var _b, _c;
    var focused = _a.focused, disabled = _a.disabled, option = _a.option, removeToken = _a.removeToken, getLabel = _a.getLabel, qa = _a.qa;
    return (React.createElement(Token, { disabled: disabled, focused: focused, variant: "icon", "data-qa": (_b = qa === null || qa === void 0 ? void 0 : qa.token) === null || _b === void 0 ? void 0 : _b.call(qa, option) },
        React.createElement(Token.Label, null, getLabel(option)),
        React.createElement(Token.Icon, { "data-close": true, icon: "x", onClick: removeToken, "data-qa": (_c = qa === null || qa === void 0 ? void 0 : qa.tokenClear) === null || _c === void 0 ? void 0 : _c.call(qa, option) })));
}
function useMultiSelect(_a) {
    var getId = _a.getId, getLabel = _a.getLabel, getGroup = _a.getGroup, menuRef = _a.menuRef, _b = _a.onChange, onChange = _b === void 0 ? noop : _b, onSearch_ = _a.onSearch, options_ = _a.options, value = _a.value, optgroups = _a.optgroups;
    var _c = React.useState(null), index = _c[0], setIndex = _c[1];
    var _d = React.useState(''), search = _d[0], setSearch = _d[1];
    var valueIds = value.reduce(function (acc, cur) {
        acc.add(getId(cur));
        return acc;
    }, new Set());
    var count = value.length;
    var options = React.useMemo(function () {
        if (onSearch_) {
            return options_;
        }
        return options_.filter(function (opt) {
            return getLabel(opt).toLowerCase().includes(search.toLowerCase());
        });
    }, [options_, onSearch_, getLabel, search]);
    var onSearch = React.useCallback(function (e) {
        if (onSearch_) {
            onSearch_(e);
        }
        setSearch(e.target.value);
        // TODO: because menu highlighting is now imperative, we have to wait
        // for a render to highlight the first item. Is there a better way
        // to schedule this?
        setTimeout(function () { var _a; return (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.highlightFirst(); }, 0);
    }, [menuRef, onSearch_]);
    var clearToken = function () { return setIndex(null); };
    var removeSelection = React.useCallback(function (selection, selected) {
        return selected.filter(function (s) { return getId(s) !== getId(selection); });
    }, [getId]);
    var onSelect = React.useCallback(function (selection) {
        var newSelected = valueIds.has(getId(selection.item))
            ? removeSelection(selection.item, value)
            : __spreadArray(__spreadArray([], value, true), [selection.item], false);
        setSearch('');
        onChange(newSelected);
    }, [setSearch, onChange, getId, removeSelection, value, valueIds]);
    var removeToken = React.useCallback(function (i) {
        onChange(removeSelection(value[i], value));
    }, [value, onChange, removeSelection]);
    var decrementToken = React.useCallback(function () {
        if (index === null) {
            // index is null, set it to the last token index
            setIndex(count - 1);
        }
        else {
            // decrementToken by one, don't go below 0
            setIndex(Math.max(0, index - 1));
        }
    }, [count, index, setIndex]);
    var incrementToken = React.useCallback(function () {
        if (index === count - 1) {
            setIndex(null);
        }
        else if (index !== null) {
            setIndex(index + 1);
        }
    }, [count, index, setIndex]);
    var onKeyDown = React.useCallback(function (event) {
        var _a, _b, _c;
        if (event.key === 'Enter') {
            (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.select(event);
        }
        if (event.key === 'ArrowUp' || event.key === 'Up') {
            (_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.prev();
        }
        if (event.key === 'ArrowDown' || event.key === 'Down') {
            (_c = menuRef.current) === null || _c === void 0 ? void 0 : _c.next();
        }
        if (search === '') {
            if (event.key === 'Backspace') {
                // to prevent accidentally deleting too many tokens, don't do anything
                // on repeating key events
                if (event.repeat) {
                    return;
                }
                // if we are focused on a token, remove it, otherwise remove the last token
                removeToken(index !== null ? index : count - 1);
                if (count >= 1) {
                    decrementToken();
                }
                else {
                    clearToken();
                }
            }
            if (event.key === 'ArrowLeft' || event.key === 'Left') {
                decrementToken();
            }
            if (event.key === 'ArrowRight' || event.key === 'Right') {
                incrementToken();
            }
        }
    }, [count, decrementToken, incrementToken, index, menuRef, removeToken, search]);
    React.useEffect(function () {
        // if we've deleted a token and our new count is less than
        // our current index, clear the index
        if (count <= (index || 0)) {
            clearToken();
        }
    }, [count, index]);
    var groupedOptions = React.useMemo(function () {
        if (!optgroups || !optgroups.length) {
            return options;
        }
        var optionsByGroup = optgroups.reduce(function (acc, _a) {
            var _b;
            var id = _a.id;
            return __assign(__assign({}, acc), (_b = {}, _b[id] = [], _b));
        }, {});
        var orphanOptions = [];
        options.forEach(function (option) {
            var groupId = getGroup(option);
            var groupOptions = optionsByGroup[groupId];
            if (!groupId || !groupOptions) {
                orphanOptions.push(option);
                return;
            }
            optionsByGroup[groupId].push(option);
        });
        if (orphanOptions.length) {
            console.warn("Could not find option groups for the options below. Make sure that every option has a proper \"groupId\" field or if \"getGroup\" is implemented correctly.", orphanOptions);
        }
        return optgroups.flatMap(function (group) {
            var groupHeader = __assign(__assign({}, group), { isGroupHeader: true });
            var groupOptions = optionsByGroup[group.id];
            if (!groupOptions.length) {
                return [];
            }
            return __spreadArray([groupHeader], groupOptions, true);
        }, []);
    }, [options, getGroup, optgroups]);
    return {
        ids: valueIds,
        index: index,
        menuRef: menuRef,
        options: groupedOptions,
        search: search,
        clearToken: clearToken,
        decrementToken: decrementToken,
        incrementToken: incrementToken,
        onSearch: onSearch,
        onSelect: onSelect,
        removeToken: removeToken,
        domHandlers: {
            onKeyDown: onKeyDown,
        },
    };
}
export var Option = React.forwardRef(function Option(_a, ref) {
    var value = _a.value, props = __rest(_a, ["value"]);
    return React.createElement(MenuImperative.Item, __assign({ ref: ref }, props, { item: value }));
});
function GroupedOptions(_a) {
    var multiselect = _a.multiselect, groupHeaderRenderer = _a.groupHeaderRenderer, optionRenderer = _a.optionRenderer, getId = _a.getId, getLabel = _a.getLabel, isOptionDisabled = _a.isOptionDisabled, qa = _a.qa;
    return (React.createElement(MenuImperative.Options, null, multiselect.options.map(function (opt) {
        if (opt.isGroupHeader) {
            var groupHeader = opt;
            return groupHeaderRenderer(groupHeader);
        }
        return optionRenderer(opt, {
            getId: getId,
            getLabel: getLabel,
            // @ts-ignore
            // Object literal may only specify known properties, and 'key' does not exist in type 'OptionRendererProps'.
            key: getId(opt),
            selected: multiselect.ids.has(getId(opt)),
            disabled: isOptionDisabled(opt),
            value: opt,
            qa: qa,
        });
    })));
}
/**

 Multi selects allow our users to choose one to many options from a list,
 presented in a dropdown. We typically see these selects on forms.

 Other select components are group select, select, and tiered select.

 @since 10.19.0

 @see [Storybook](https://procore.github.io/core/latest/?path=/story/multiselect--basic)

 @see [Design Guidelines](https://design.procore.com/multi-select)

 */
var MultiSelect_ = React.forwardRef(function MultiSelect(_a, ref) {
    var _b = _a.afterHide, afterHide = _b === void 0 ? noop : _b, _c = _a.afterShow, afterShow = _c === void 0 ? noop : _c, _d = _a.beforeHide, beforeHide = _d === void 0 ? returnTrue : _d, _e = _a.beforeShow, beforeShow = _e === void 0 ? returnTrue : _e, _f = _a.block, block = _f === void 0 ? false : _f, _g = _a.disabled, disabled = _g === void 0 ? false : _g, _h = _a.emptyMessage, emptyMessage = _h === void 0 ? 'No results' : _h, _j = _a.error, error = _j === void 0 ? false : _j, _k = _a.getId, getId = _k === void 0 ? defaultGetId : _k, _l = _a.getLabel, getLabel = _l === void 0 ? defaultGetLabel : _l, _m = _a.getGroup, getGroup = _m === void 0 ? defaultGetGroup : _m, _o = _a.loading, loading = _o === void 0 ? false : _o, _p = _a.onChange, onChange = _p === void 0 ? noop : _p, onScrollBottom = _a.onScrollBottom, onSearch = _a.onSearch, _q = _a.options, options = _q === void 0 ? emptyArray : _q, _r = _a.isOptionDisabled, isOptionDisabled = _r === void 0 ? defaultIsOptionDisabled : _r, _s = _a.optionRenderer, optionRenderer = _s === void 0 ? defaultOptionRenderer : _s, optgroups = _a.optgroups, _t = _a.groupHeaderRenderer, groupHeaderRenderer = _t === void 0 ? defaultGroupHeaderRenderer : _t, _u = _a.tokenRenderer, tokenRenderer = _u === void 0 ? defaultTokenRenderer : _u, _v = _a.placeholder, placeholder = _v === void 0 ? 'Select Values' : _v, _w = _a.placement, placement = _w === void 0 ? 'bottom-left' : _w, _x = _a.value, value = _x === void 0 ? emptyArray : _x, _y = _a.tabIndex, tabIndex = _y === void 0 ? 0 : _y, _z = _a.qa, qa = _z === void 0 ? {} : _z, props = __rest(_a, ["afterHide", "afterShow", "beforeHide", "beforeShow", "block", "disabled", "emptyMessage", "error", "getId", "getLabel", "getGroup", "loading", "onChange", "onScrollBottom", "onSearch", "options", "isOptionDisabled", "optionRenderer", "optgroups", "groupHeaderRenderer", "tokenRenderer", "placeholder", "placement", "value", "tabIndex", "qa"]);
    var ownRef = React.useRef();
    var targetRef = ref || ownRef;
    var searchRef = React.useRef(null);
    var menuRef = React.useRef(null);
    var multiselect = useMultiSelect({
        getId: getId,
        getLabel: getLabel,
        getGroup: getGroup,
        menuRef: menuRef,
        onSearch: onSearch,
        options: options,
        onChange: function (value) {
            onChange(value);
            setFocusTo(searchRef);
        },
        value: value,
        optgroups: optgroups,
    });
    var hasClearIcon = value.length > 0 && !loading;
    var isNavigatingTokens = multiselect.index !== null;
    var overlay = (React.createElement(StyledMultiSelectMenu, { shadowStrength: 2 },
        React.createElement(MenuImperative, { ref: menuRef, onScrollBottom: onScrollBottom, onSelect: multiselect.onSelect, "data-qa": "multi-select-menu" }, multiselect.options.length ? (React.createElement(GroupedOptions, { multiselect: multiselect, getId: getId, getLabel: getLabel, isOptionDisabled: isOptionDisabled, groupHeaderRenderer: groupHeaderRenderer, optionRenderer: optionRenderer, qa: qa })) : (React.createElement(Box, { padding: "md lg" },
            React.createElement(Typography, { color: "gray45", intent: "small", italic: true }, emptyMessage))))));
    return (React.createElement(OverlayTrigger, { canFlip: true, afterHide: function () {
            multiselect.clearToken();
            afterHide();
        }, afterShow: function () {
            var _a;
            multiselect.clearToken();
            (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.highlightFirst();
            afterShow();
        }, beforeHide: function (e) {
            // if we are clicking somewhere in the target, set focus on the search
            if (e instanceof MouseEvent && isEventSource(targetRef, e)) {
                setFocusTo(searchRef);
                return false;
            }
            return beforeHide(e);
        }, beforeShow: function (e) {
            setFocusTo(searchRef);
            return beforeShow(e);
        }, hideKeys: {
            overlay: overlayHideKeys,
            target: targetHideKeys,
        }, overlay: overlay, placement: placement, ref: targetRef, showKeys: targetShowKeys, trigger: "click" }, function (_a) {
        var disable = _a.disable, enable = _a.enable, isVisible = _a.isVisible;
        return (React.createElement(StyledMultiSelectButton, __assign({ "$block": block, "$error": error, "$emptyValue": value.length === 0, "$disabled": disabled, "$hasClearIcon": hasClearIcon, "$loading": loading, "$open": isVisible }, props),
            React.createElement(StyledMultiSelectValues, null,
                value.map(function (selection, i) {
                    return (React.createElement(StyledMultiSelectToken, { key: getId(selection) }, tokenRenderer({
                        option: selection,
                        focused: i === multiselect.index,
                        disabled: disabled,
                        getLabel: getLabel,
                        removeToken: function () {
                            multiselect.removeToken(i);
                        },
                        qa: qa,
                    })));
                }),
                React.createElement(StyledMultiSelectSearch, null,
                    React.createElement(StyledMultiSelectSearchInput, { "data-qa": "core-multiselect-input", "$isNavigatingTokens": isNavigatingTokens, disabled: disabled, onBlur: multiselect.clearToken, onChange: function (e) {
                            multiselect.onSearch(e);
                        }, onKeyDown: function (e) {
                            if (isNavigatingTokens && e.key !== 'Tab') {
                                // if we are navigating tokens don't do any input but allow Tab
                                e.preventDefault();
                            }
                            if (isVisible) {
                                // if the overlay is visible, close it and keep focus
                                if (e.key === 'Tab') {
                                    e.preventDefault();
                                    disable(e);
                                }
                            }
                            else {
                                // if we type in an alphanumeric character, show the overlay
                                if (isAlphaNumeric(e.key)) {
                                    enable(e);
                                }
                            }
                            multiselect.domHandlers.onKeyDown(e);
                        }, placeholder: value && value.length > 0 ? '' : placeholder, ref: searchRef, tabIndex: tabIndex, value: multiselect.search }))),
            React.createElement(StyledMultiSelectSearchIcon, null,
                React.createElement(StyledMultiSelectClearIcon, { "data-close": true, "data-qa": "core-multiselect-clear", size: "sm", variant: "tertiary", disabled: disabled, icon: React.createElement(Clear, null), onClick: function () { return onChange([]); }, tabIndex: -1 }),
                loading ? (React.createElement(Spinner, { color: "blue50", size: "xs" })) : (React.createElement(StyledMultiSelectArrowContainer, { "data-qa": "multiselect-select-arrow", onClick: function (e) { return isVisible && disable(e); } },
                    React.createElement(StyledMultiSelectArrow, null))))));
    }));
});
MultiSelect_.displayName = 'MultiSelect';
export var MultiSelect = addSubcomponents({
    Option: Option,
}, MultiSelect_);
export default MultiSelect;
//# sourceMappingURL=MultiSelect.js.map