"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const vueuc_1 = require("vueuc");
const vooks_1 = require("vooks");
const vdirs_1 = require("vdirs");
const treemate_1 = require("treemate");
const Tree_1 = require("../../tree/src/Tree");
const _internal_1 = require("../../_internal");
const tree_1 = require("../../tree");
const empty_1 = require("../../empty");
const _mixins_1 = require("../../_mixins");
const _utils_1 = require("../../_utils");
const styles_1 = require("../styles");
const interface_1 = require("./interface");
const utils_1 = require("./utils");
const index_cssr_1 = require("./styles/index.cssr");
const props = Object.assign(Object.assign(Object.assign(Object.assign({}, _mixins_1.useTheme.props), { bordered: {
        type: Boolean,
        default: true
    }, cascade: Boolean, checkable: Boolean, clearable: Boolean, consistentMenuWidth: {
        type: Boolean,
        default: true
    }, defaultShow: Boolean, defaultValue: {
        type: [String, Number, Array],
        default: null
    }, disabled: Boolean, filterable: Boolean, leafOnly: Boolean, maxTagCount: [String, Number], multiple: Boolean, options: {
        type: Array,
        default: () => []
    }, placeholder: String, placement: {
        type: String,
        default: 'bottom-start'
    }, show: {
        type: Boolean,
        default: undefined
    }, size: String, value: [String, Number, Array], to: _utils_1.useAdjustedTo.propTo, virtualScroll: {
        type: Boolean,
        default: true
    } }), Tree_1.treeSharedProps), { onBlur: Function, onFocus: Function, onUpdateShow: [Function, Array], onUpdateValue: [Function, Array], 'onUpdate:value': [Function, Array], 'onUpdate:show': [Function, Array] });
exports.default = vue_1.defineComponent({
    name: 'TreeSelect',
    props,
    setup(props) {
        const followerInstRef = vue_1.ref(null);
        const triggerInstRef = vue_1.ref(null);
        const treeInstRef = vue_1.ref(null);
        const menuElRef = vue_1.ref(null);
        const { mergedClsPrefixRef, namespaceRef } = _mixins_1.useConfig(props);
        const { localeRef } = _mixins_1.useLocale('Select');
        const { mergedSizeRef, nTriggerFormBlur, nTriggerFormChange, nTriggerFormFocus, nTriggerFormInput } = _mixins_1.useFormItem(props);
        const uncontrolledValueRef = vue_1.ref(props.defaultValue);
        const controlledValueRef = vue_1.toRef(props, 'value');
        const mergedValueRef = vooks_1.useMergedState(controlledValueRef, uncontrolledValueRef);
        const uncontrolledShowRef = vue_1.ref(props.defaultShow);
        const controlledShowRef = vue_1.toRef(props, 'show');
        const mergedShowRef = vooks_1.useMergedState(controlledShowRef, uncontrolledShowRef);
        const patternRef = vue_1.ref('');
        const filteredTreeInfoRef = vue_1.computed(() => {
            if (!props.filterable) {
                return {
                    filteredTree: props.options,
                    highlightKeySet: undefined,
                    expandedKeys: undefined
                };
            }
            const { value: pattern } = patternRef;
            if (!pattern.length || !props.filter) {
                return {
                    filteredTree: props.options,
                    highlightKeySet: undefined,
                    expandedKeys: undefined
                };
            }
            return utils_1.filterTree(props.options, props.filter, pattern);
        });
        // used to resolve selected options
        const dataTreeMateRef = vue_1.computed(() => treemate_1.createTreeMate(props.options, Tree_1.treeMateOptions));
        const displayTreeMateRef = vue_1.computed(() => treemate_1.createTreeMate(filteredTreeInfoRef.value.filteredTree, Tree_1.treeMateOptions));
        const { value: initMergedValue } = mergedValueRef;
        const pendingNodeKeyRef = vue_1.ref(props.checkable
            ? null
            : Array.isArray(initMergedValue) && initMergedValue.length
                ? initMergedValue[initMergedValue.length - 1]
                : null);
        const mergedCascadeRef = vue_1.computed(() => {
            return props.multiple && props.cascade;
        });
        // The same logic as tree, now it's not that complex so I don't extract a
        // function to reuse it.
        const uncontrolledExpandedKeysRef = vue_1.ref(props.defaultExpandAll
            ? displayTreeMateRef.value.getNonLeafKeys()
            : props.defaultExpandedKeys || props.expandedKeys);
        const controlledExpandedKeysRef = vue_1.toRef(props, 'expandedKeys');
        const mergedExpandedKeysRef = vooks_1.useMergedState(controlledExpandedKeysRef, uncontrolledExpandedKeysRef);
        const focusedRef = vue_1.ref(false);
        const mergedPlaceholderRef = vue_1.computed(() => {
            const { placeholder } = props;
            if (placeholder !== undefined)
                return placeholder;
            return localeRef.value.placeholder;
        });
        const treeSelectedKeysRef = vue_1.computed(() => {
            if (props.checkable)
                return [];
            const { value: mergedValue } = mergedValueRef;
            const { multiple } = props;
            return Array.isArray(mergedValue)
                ? multiple
                    ? mergedValue
                    : []
                : multiple
                    ? []
                    : mergedValue === null
                        ? []
                        : [mergedValue];
        });
        const treeCheckedKeysRef = vue_1.computed(() => {
            if (!props.checkable)
                return [];
            const { value: mergedValue } = mergedValueRef;
            if (props.multiple) {
                if (Array.isArray(mergedValue))
                    return mergedValue;
                else
                    return [];
            }
            else {
                if (mergedValue === null || Array.isArray(mergedValue))
                    return [];
                else
                    return [mergedValue];
            }
        });
        const selectedOptionRef = vue_1.computed(() => {
            if (props.multiple)
                return null;
            const { value: mergedValue } = mergedValueRef;
            if (!Array.isArray(mergedValue) && mergedValue !== null) {
                const tmNode = dataTreeMateRef.value.getNode(mergedValue);
                if (tmNode !== null)
                    return utils_1.treeOption2SelectOption(tmNode.rawNode);
            }
            return null;
        });
        const selectedOptionsRef = vue_1.computed(() => {
            if (!props.multiple)
                return null;
            const { value: mergedValue } = mergedValueRef;
            if (Array.isArray(mergedValue)) {
                const res = [];
                const { value: treeMate } = dataTreeMateRef;
                mergedValue.forEach((value) => {
                    const tmNode = treeMate.getNode(value);
                    if (tmNode !== null)
                        res.push(utils_1.treeOption2SelectOption(tmNode.rawNode));
                });
                return res;
            }
            return [];
        });
        const menuPaddingRef = vue_1.computed(() => {
            const { self: { menuPadding } } = themeRef.value;
            return menuPadding;
        });
        function focusSelection() {
            var _a;
            (_a = triggerInstRef.value) === null || _a === void 0 ? void 0 : _a.focus();
        }
        function focusSelectionInput() {
            var _a;
            (_a = triggerInstRef.value) === null || _a === void 0 ? void 0 : _a.focusInput();
        }
        function doUpdateShow(value) {
            const { onUpdateShow, 'onUpdate:show': _onUpdateShow } = props;
            if (onUpdateShow)
                _utils_1.call(onUpdateShow, value);
            if (_onUpdateShow)
                _utils_1.call(_onUpdateShow, value);
            uncontrolledShowRef.value = value;
        }
        function doUpdateValue(value) {
            const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props;
            if (onUpdateValue)
                _utils_1.call(onUpdateValue, value);
            if (_onUpdateValue)
                _utils_1.call(_onUpdateValue, value);
            uncontrolledValueRef.value = value;
            nTriggerFormInput();
            nTriggerFormChange();
        }
        function doUpdateExpandedKeys(keys) {
            const { onUpdateExpandedKeys, 'onUpdate:expandedKeys': _onUpdateExpandedKeys } = props;
            if (onUpdateExpandedKeys)
                _utils_1.call(onUpdateExpandedKeys, keys);
            if (_onUpdateExpandedKeys)
                _utils_1.call(_onUpdateExpandedKeys, keys);
            uncontrolledExpandedKeysRef.value = keys;
        }
        function doFocus(e) {
            const { onFocus } = props;
            if (onFocus)
                onFocus(e);
            nTriggerFormFocus();
        }
        function doBlur(e) {
            closeMenu();
            const { onBlur } = props;
            if (onBlur)
                onBlur(e);
            nTriggerFormBlur();
        }
        function closeMenu() {
            doUpdateShow(false);
        }
        function openMenu() {
            if (!props.disabled) {
                patternRef.value = '';
                doUpdateShow(true);
                if (props.filterable) {
                    focusSelectionInput();
                }
            }
        }
        function handleMenuLeave() {
            patternRef.value = '';
        }
        function handleMenuClickoutside(e) {
            var _a;
            if (mergedShowRef.value) {
                if (!((_a = triggerInstRef.value) === null || _a === void 0 ? void 0 : _a.$el.contains(e.target))) {
                    // outside select, don't need to return focus
                    closeMenu();
                }
            }
        }
        function handleTriggerClick() {
            if (props.disabled)
                return;
            if (!mergedShowRef.value) {
                openMenu();
            }
            else {
                if (!props.filterable) {
                    // already focused, don't need to return focus
                    closeMenu();
                }
            }
        }
        function handleUpdateSelectedKeys(keys) {
            var _a;
            if (props.checkable && props.multiple) {
                return;
            }
            if (props.multiple) {
                doUpdateValue(keys);
            }
            else {
                doUpdateValue((_a = keys[0]) !== null && _a !== void 0 ? _a : null);
                closeMenu();
                if (!props.filterable) {
                    // Currently it is not necessary. However if there is an action slot,
                    // it will be useful. So just leave it here.
                    focusSelection();
                }
            }
            if (props.filterable) {
                focusSelectionInput();
                patternRef.value = '';
            }
        }
        function handleUpdateCheckedKeys(keys) {
            // only in checkable & multiple mode, we use tree's check update
            if (props.checkable && props.multiple) {
                doUpdateValue(keys);
                if (props.filterable) {
                    focusSelectionInput();
                    patternRef.value = '';
                }
            }
        }
        function handleTriggerFocus(e) {
            var _a;
            if ((_a = menuElRef.value) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget))
                return;
            focusedRef.value = true;
            doFocus(e);
        }
        function handleTriggerBlur(e) {
            var _a;
            if ((_a = menuElRef.value) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget))
                return;
            focusedRef.value = false;
            doBlur(e);
        }
        function handleMenuFocusin(e) {
            var _a, _b, _c;
            if (((_a = menuElRef.value) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget)) ||
                ((_c = (_b = triggerInstRef.value) === null || _b === void 0 ? void 0 : _b.$el) === null || _c === void 0 ? void 0 : _c.contains(e.relatedTarget))) {
                return;
            }
            focusedRef.value = true;
            doFocus(e);
        }
        function handleMenuFocusout(e) {
            var _a, _b, _c;
            if (((_a = menuElRef.value) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget)) ||
                ((_c = (_b = triggerInstRef.value) === null || _b === void 0 ? void 0 : _b.$el) === null || _c === void 0 ? void 0 : _c.contains(e.relatedTarget))) {
                return;
            }
            focusedRef.value = false;
            doBlur(e);
        }
        function handleClear(e) {
            e.stopPropagation();
            const { multiple } = props;
            if (!multiple && props.filterable) {
                closeMenu();
            }
            if (multiple) {
                doUpdateValue([]);
            }
            else {
                doUpdateValue(null);
            }
        }
        function handleDeleteOption(option) {
            // only work for multiple mode
            const { value: mergedValue } = mergedValueRef;
            if (Array.isArray(mergedValue)) {
                const index = mergedValue.findIndex((key) => key === option.value);
                if (~index) {
                    if (props.checkable) {
                        const { checkedKeys } = dataTreeMateRef.value.uncheck(option.value, mergedValue, {
                            cascade: mergedCascadeRef.value
                        });
                        doUpdateValue(checkedKeys);
                    }
                    else {
                        const nextValue = Array.from(mergedValue);
                        nextValue.splice(index, 1);
                        doUpdateValue(nextValue);
                    }
                }
            }
        }
        function handlePatternInput(e) {
            const { value } = e.target;
            patternRef.value = value;
        }
        function handleKeydown(e) {
            const { value: treeInst } = treeInstRef;
            if (treeInst) {
                treeInst.handleKeydown(e);
            }
        }
        function handleKeyup(e) {
            if (e.code === 'Enter' || e.code === 'NumpadEnter') {
                if (mergedShowRef.value) {
                    treeHandleKeyup(e);
                    if (!props.multiple) {
                        closeMenu();
                        focusSelection();
                    }
                }
                else {
                    openMenu();
                }
                e.preventDefault();
            }
            else if (e.code === 'Escape') {
                closeMenu();
                focusSelection();
            }
            else {
                if (mergedShowRef.value) {
                    treeHandleKeyup(e);
                }
                else if (e.code === 'ArrowDown') {
                    openMenu();
                }
            }
        }
        function treeHandleKeyup(e) {
            const { value: treeInst } = treeInstRef;
            if (treeInst) {
                treeInst.handleKeyup(e);
            }
        }
        function handleTabOut() {
            closeMenu();
            focusSelection();
        }
        function handleMenuMousedown(e) {
            // If there's an action slot later, we need to check if mousedown happens
            // in action panel
            e.preventDefault();
        }
        vue_1.provide(interface_1.treeSelectInjectionKey, {
            pendingNodeKeyRef
        });
        function syncPosition() {
            var _a;
            (_a = followerInstRef.value) === null || _a === void 0 ? void 0 : _a.syncPosition();
        }
        vue_1.watch(mergedValueRef, () => {
            if (!mergedShowRef.value)
                return;
            void vue_1.nextTick(syncPosition);
        });
        let memorizedExpandedKeys;
        vue_1.watch(patternRef, (value, oldValue) => {
            if (!value.length) {
                if (memorizedExpandedKeys !== undefined) {
                    doUpdateExpandedKeys(memorizedExpandedKeys);
                }
            }
            else {
                if (!oldValue.length) {
                    memorizedExpandedKeys = mergedExpandedKeysRef.value;
                }
                const { expandedKeys } = filteredTreeInfoRef.value;
                if (expandedKeys !== undefined) {
                    doUpdateExpandedKeys(expandedKeys);
                }
            }
        });
        const themeRef = _mixins_1.useTheme('TreeSelect', 'TreeSelect', index_cssr_1.default, styles_1.treeSelectLight, props, mergedClsPrefixRef);
        return {
            menuElRef,
            triggerInstRef,
            followerInstRef,
            treeInstRef,
            mergedClsPrefix: mergedClsPrefixRef,
            mergedValue: mergedValueRef,
            mergedShow: mergedShowRef,
            namespace: namespaceRef,
            adjustedTo: _utils_1.useAdjustedTo(props),
            isMounted: vooks_1.useIsMounted(),
            focused: focusedRef,
            filteredTreeInfo: filteredTreeInfoRef,
            dataTreeMate: dataTreeMateRef,
            displayTreeMate: displayTreeMateRef,
            menuPadding: menuPaddingRef,
            mergedPlaceholder: mergedPlaceholderRef,
            mergedExpandedKeys: mergedExpandedKeysRef,
            treeSelectedKeys: treeSelectedKeysRef,
            treeCheckedKeys: treeCheckedKeysRef,
            mergedSize: mergedSizeRef,
            selectedOption: selectedOptionRef,
            selectedOptions: selectedOptionsRef,
            pattern: patternRef,
            pendingNodeKey: pendingNodeKeyRef,
            mergedCascade: mergedCascadeRef,
            doUpdateExpandedKeys,
            handleMenuLeave,
            handleTriggerClick,
            handleMenuClickoutside,
            handleUpdateSelectedKeys,
            handleUpdateCheckedKeys,
            handleTriggerFocus,
            handleTriggerBlur,
            handleMenuFocusin,
            handleMenuFocusout,
            handleClear,
            handleDeleteOption,
            handlePatternInput,
            handleKeydown,
            handleKeyup,
            handleTabOut,
            handleMenuMousedown,
            cssVars: vue_1.computed(() => {
                const { common: { cubicBezierEaseInOut }, self: { menuBoxShadow, menuBorderRadius, menuColor, menuHeight } } = themeRef.value;
                return {
                    '--menu-box-shadow': menuBoxShadow,
                    '--menu-border-radius': menuBorderRadius,
                    '--menu-color': menuColor,
                    '--menu-height': menuHeight,
                    '--bezier': cubicBezierEaseInOut
                };
            }),
            mergedTheme: themeRef
        };
    },
    render() {
        const { mergedTheme, mergedClsPrefix } = this;
        return (vue_1.h("div", { class: `${mergedClsPrefix}-tree-select` },
            vue_1.h(vueuc_1.VBinder, null, {
                default: () => [
                    vue_1.h(vueuc_1.VTarget, null, {
                        default: () => (vue_1.h(_internal_1.NInternalSelection, { ref: "triggerInstRef", focused: this.focused, clsPrefix: mergedClsPrefix, theme: mergedTheme.peers.InternalSelection, themeOverrides: mergedTheme.peerOverrides.InternalSelection, selectedOption: this.selectedOption, selectedOptions: this.selectedOptions, size: this.mergedSize, bordered: this.bordered, placeholder: this.mergedPlaceholder, disabled: this.disabled, active: this.mergedShow, multiple: this.multiple, maxTagCount: this.maxTagCount, showArrow: true, filterable: this.filterable, clearable: this.clearable, pattern: this.pattern, onPatternInput: this.handlePatternInput, onClear: this.handleClear, onClick: this.handleTriggerClick, onFocus: this.handleTriggerFocus, onBlur: this.handleTriggerBlur, onDeleteOption: this.handleDeleteOption, onKeydown: this.handleKeydown, onKeyup: this.handleKeyup }))
                    }),
                    vue_1.h(vueuc_1.VFollower, { ref: "followerInstRef", show: this.mergedShow, placement: this.placement, to: this.adjustedTo, teleportDisabled: this.adjustedTo === _utils_1.useAdjustedTo.tdkey, containerClass: this.namespace, width: this.consistentMenuWidth ? 'target' : undefined, minWidth: "target" }, {
                        default: () => (vue_1.h(vue_1.Transition, { name: "fade-in-scale-up-transition", appear: this.isMounted, onLeave: this.handleMenuLeave }, {
                            default: () => {
                                if (!this.mergedShow)
                                    return null;
                                const { mergedClsPrefix, filteredTreeInfo, checkable, multiple } = this;
                                return vue_1.withDirectives(vue_1.h("div", { class: `${mergedClsPrefix}-tree-select-menu`, ref: "menuElRef", style: this.cssVars, tabindex: 0, onMousedown: this.handleMenuMousedown, onKeyup: this.handleKeyup, onKeydown: this.handleKeydown, onFocusin: this.handleMenuFocusin, onFocusout: this.handleMenuFocusout },
                                    filteredTreeInfo.filteredTree.length ? (vue_1.h(tree_1.NTree, { ref: "treeInstRef", blockLine: true, animated: false, data: filteredTreeInfo.filteredTree, cancelable: multiple, theme: mergedTheme.peers.Tree, themeOverrides: mergedTheme.peerOverrides.Tree, defaultExpandAll: this.defaultExpandAll, defaultExpandedKeys: this.defaultExpandedKeys, expandedKeys: this.mergedExpandedKeys, checkedKeys: this.treeCheckedKeys, selectedKeys: this.treeSelectedKeys, checkable: checkable, cascade: this.mergedCascade, leafOnly: this.leafOnly, multiple: this.multiple, virtualScroll: this.consistentMenuWidth &&
                                            this.virtualScroll, internalDataTreeMate: this.dataTreeMate, internalDisplayTreeMate: this.displayTreeMate, internalHighlightKeySet: filteredTreeInfo.highlightKeySet, internalCheckOnSelect: true, internalScrollable: true, internalScrollablePadding: this.menuPadding, internalFocusable: false, internalCheckboxFocusable: false, onUpdateCheckedKeys: this.handleUpdateCheckedKeys, onUpdateExpandedKeys: this.doUpdateExpandedKeys, onUpdateSelectedKeys: this.handleUpdateSelectedKeys })) : (vue_1.h("div", { class: `${mergedClsPrefix}-tree-select-menu__empty` },
                                        vue_1.h(empty_1.NEmpty, { theme: mergedTheme.peers.Empty, themeOverrides: mergedTheme.peerOverrides.Empty }))),
                                    vue_1.h(_internal_1.NBaseFocusDetector, { onFocus: this.handleTabOut })), [[vdirs_1.clickoutside, this.handleMenuClickoutside]]);
                            }
                        }))
                    })
                ]
            })));
    }
});
