"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const vue_1 = require("vue");
const treemate_1 = require("treemate");
const vueuc_1 = require("vueuc");
const vooks_1 = require("vooks");
const input_1 = require("../../input");
const _internal_1 = require("../../_internal");
const _utils_1 = require("../../_utils");
const _mixins_1 = require("../../_mixins");
const styles_1 = require("../styles");
const utils_1 = require("./utils");
const index_cssr_1 = require("./styles/index.cssr");
const mentionProps = Object.assign(Object.assign({}, _mixins_1.useTheme.props), { to: _utils_1.useAdjustedTo.propTo, autosize: [Boolean, Object], options: {
        type: Array,
        default: []
    }, type: {
        type: String,
        default: 'input'
    }, separator: {
        type: String,
        validator: (separator) => {
            if (separator.length !== 1) {
                _utils_1.warn('mention', "`separator`'s length must be 1.");
                return false;
            }
            return true;
        },
        default: ' '
    }, bordered: {
        type: Boolean,
        default: undefined
    }, disabled: Boolean, value: String, defaultValue: {
        type: String,
        default: ''
    }, loading: Boolean, prefix: {
        type: [String, Array],
        default: '@'
    }, placeholder: {
        type: String,
        default: ''
    }, size: String, 'onUpdate:value': [Array, Function], renderLabel: Function, onUpdateValue: [Array, Function], onSearch: Function, onSelect: Function, onFocus: Function, onBlur: Function, 
    // private
    internalDebug: Boolean });
exports.default = vue_1.defineComponent({
    name: 'Mention',
    props: mentionProps,
    setup(props) {
        const { namespaceRef, mergedClsPrefixRef, mergedBorderedRef } = _mixins_1.useConfig(props);
        const themeRef = _mixins_1.useTheme('Mention', 'Mention', index_cssr_1.default, styles_1.mentionLight, props, mergedClsPrefixRef);
        const formItem = _mixins_1.useFormItem(props);
        const inputInstRef = vue_1.ref(null);
        const cursorRef = vue_1.ref(null);
        const followerRef = vue_1.ref(null);
        const partialPatternRef = vue_1.ref('');
        let cachedPrefix = null;
        // cached pattern end is for partial pattern
        // for example @abc|def
        // end is after `c`
        let cachedPartialPatternStart = null;
        let cachedPartialPatternEnd = null;
        const filteredOptionsRef = vue_1.computed(() => {
            const { value: pattern } = partialPatternRef;
            return props.options.filter((option) => {
                if (!pattern)
                    return true;
                if (typeof option.label === 'string') {
                    return option.label.startsWith(pattern);
                }
                return option.value.startsWith(pattern);
            });
        });
        const treeMateRef = vue_1.computed(() => {
            return treemate_1.createTreeMate(filteredOptionsRef.value, {
                getKey: (v) => {
                    return v.value;
                }
            });
        });
        const selectMenuInstRef = vue_1.ref(null);
        const showMenuRef = vue_1.ref(false);
        const uncontrolledValueRef = vue_1.ref(props.defaultValue);
        const controlledValueRef = vue_1.toRef(props, 'value');
        const mergedValueRef = vooks_1.useMergedState(controlledValueRef, uncontrolledValueRef);
        function doUpdateShowMenu(show) {
            if (props.disabled)
                return;
            if (!show) {
                cachedPrefix = null;
                cachedPartialPatternStart = null;
                cachedPartialPatternEnd = null;
            }
            showMenuRef.value = show;
        }
        function doUpdateValue(value) {
            const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props;
            const { nTriggerFormChange, nTriggerFormInput } = formItem;
            if (_onUpdateValue) {
                _utils_1.call(_onUpdateValue, value);
            }
            if (onUpdateValue) {
                _utils_1.call(onUpdateValue, value);
            }
            nTriggerFormInput();
            nTriggerFormChange();
            uncontrolledValueRef.value = value;
        }
        function getInputEl() {
            return props.type === 'input'
                ? inputInstRef.value.inputElRef
                : inputInstRef.value.textareaElRef;
        }
        function deriveShowMenu() {
            var _a;
            const inputEl = getInputEl();
            if (document.activeElement !== inputEl) {
                doUpdateShowMenu(false);
                return;
            }
            const { selectionEnd } = inputEl;
            if (selectionEnd === null) {
                doUpdateShowMenu(false);
                return;
            }
            const inputValue = inputEl.value;
            const { separator } = props;
            const { prefix } = props;
            const prefixArray = typeof prefix === 'string' ? [prefix] : prefix;
            for (let i = selectionEnd - 1; i >= 0; --i) {
                const char = inputValue[i];
                if (char === separator || char === '\n' || char === '\r') {
                    doUpdateShowMenu(false);
                    return;
                }
                if (prefixArray.includes(char)) {
                    const partialPattern = inputValue.slice(i + 1, selectionEnd);
                    doUpdateShowMenu(true);
                    (_a = props.onSearch) === null || _a === void 0 ? void 0 : _a.call(props, partialPattern, char);
                    partialPatternRef.value = partialPattern;
                    cachedPrefix = char;
                    cachedPartialPatternStart = i + 1;
                    cachedPartialPatternEnd = selectionEnd;
                    return;
                }
            }
            doUpdateShowMenu(false);
        }
        function syncCursor() {
            const { value: cursorAnchor } = cursorRef;
            if (!cursorAnchor)
                return;
            const inputEl = getInputEl();
            const cursorPos = utils_1.getRelativePosition(inputEl);
            cursorPos.left += inputEl.parentElement.offsetLeft;
            cursorAnchor.style.left = `${cursorPos.left}px`;
            cursorAnchor.style.top = `${cursorPos.top + cursorPos.height}px`;
        }
        function syncPosition() {
            var _a;
            if (!showMenuRef.value)
                return;
            (_a = followerRef.value) === null || _a === void 0 ? void 0 : _a.syncPosition();
        }
        function handleInputUpdateValue(value) {
            doUpdateValue(value);
            // Vue update is mirco task.
            // So DOM must have been done when sync start in marco task.
            // I can't use nextTick(), Chrome doesn't update scrollLeft of INPUT
            // element is immediatelly updated. The behavior is wired but that's what
            // happens.
            syncAfterCursorMove();
        }
        function syncAfterCursorMove() {
            setTimeout(() => {
                syncCursor();
                deriveShowMenu();
                void vue_1.nextTick().then(syncPosition);
            }, 0);
        }
        function handleInputKeyDown(e) {
            var _a, _b;
            if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
                if ((_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.isCompositing)
                    return;
                syncAfterCursorMove();
            }
            else if (e.code === 'ArrowUp' ||
                e.code === 'ArrowDown' ||
                e.code === 'Enter' ||
                e.code === 'NumpadEnter') {
                if ((_b = inputInstRef.value) === null || _b === void 0 ? void 0 : _b.isCompositing)
                    return;
                const { value: selectMenuInst } = selectMenuInstRef;
                if (showMenuRef.value) {
                    if (selectMenuInst) {
                        e.preventDefault();
                        if (e.code === 'ArrowUp') {
                            selectMenuInst.prev();
                        }
                        else if (e.code === 'ArrowDown') {
                            selectMenuInst.next();
                        }
                        else {
                            // Enter
                            const option = selectMenuInst.getPendingOption();
                            if (option) {
                                handleSelect(option);
                            }
                            else {
                                doUpdateShowMenu(false);
                            }
                        }
                    }
                }
                else {
                    syncAfterCursorMove();
                }
            }
        }
        function handleInputFocus(e) {
            const { onFocus } = props;
            onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
            const { nTriggerFormFocus } = formItem;
            nTriggerFormFocus();
            syncAfterCursorMove();
        }
        function handleInputBlur(e) {
            const { onBlur } = props;
            onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
            const { nTriggerFormBlur } = formItem;
            nTriggerFormBlur();
            doUpdateShowMenu(false);
        }
        function handleSelect(option) {
            var _a;
            if (cachedPrefix === null ||
                cachedPartialPatternStart === null ||
                cachedPartialPatternEnd === null) {
                if (process.env.NODE_ENV !== 'production') {
                    _utils_1.warn('mention', 'Cache works unexpectly, this is probably a bug. Please create an issue.');
                }
                return;
            }
            const { value } = option;
            const inputEl = getInputEl();
            const inputValue = inputEl.value;
            const { separator } = props;
            const nextEndPart = inputValue.slice(cachedPartialPatternEnd);
            const alreadySeparated = nextEndPart.startsWith(separator);
            const nextMiddlePart = `${value}${alreadySeparated ? '' : separator}`;
            doUpdateValue(inputValue.slice(0, cachedPartialPatternStart) +
                nextMiddlePart +
                nextEndPart);
            (_a = props.onSelect) === null || _a === void 0 ? void 0 : _a.call(props, option, cachedPrefix);
            const nextSelectionEnd = cachedPartialPatternStart +
                nextMiddlePart.length +
                (alreadySeparated ? 1 : 0);
            void vue_1.nextTick().then(() => {
                // input value is updated
                inputEl.selectionStart = nextSelectionEnd;
                inputEl.selectionEnd = nextSelectionEnd;
                deriveShowMenu();
            });
        }
        function handleInputMouseDown() {
            if (!props.disabled) {
                syncAfterCursorMove();
            }
        }
        return {
            namespace: namespaceRef,
            mergedClsPrefix: mergedClsPrefixRef,
            mergedBordered: mergedBorderedRef,
            mergedSize: formItem.mergedSizeRef,
            mergedTheme: themeRef,
            treeMate: treeMateRef,
            selectMenuInstRef,
            inputInstRef,
            cursorRef,
            followerRef,
            showMenu: showMenuRef,
            adjustedTo: _utils_1.useAdjustedTo(props),
            isMounted: vooks_1.useIsMounted(),
            mergedValue: mergedValueRef,
            handleInputFocus,
            handleInputBlur,
            handleInputUpdateValue,
            handleInputKeyDown,
            handleSelect,
            handleInputMouseDown,
            cssVars: vue_1.computed(() => {
                const { self: { menuBoxShadow } } = themeRef.value;
                return {
                    '--menu-box-shadow': menuBoxShadow
                };
            })
        };
    },
    render() {
        const { mergedTheme, mergedClsPrefix, $slots } = this;
        return (vue_1.h("div", { class: `${mergedClsPrefix}-mention` },
            vue_1.h(input_1.NInput, { themeOverrides: mergedTheme.peerOverrides.Input, theme: mergedTheme.peers.Input, size: this.mergedSize, autosize: this.autosize, type: this.type, ref: "inputInstRef", placeholder: this.placeholder, onMousedown: this.handleInputMouseDown, onUpdateValue: this.handleInputUpdateValue, onKeydown: this.handleInputKeyDown, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, bordered: this.mergedBordered, disabled: this.disabled, value: this.mergedValue }),
            vue_1.h(vueuc_1.VBinder, null, {
                default: () => [
                    vue_1.h(vueuc_1.VTarget, null, {
                        default: () => {
                            const style = {
                                position: 'absolute',
                                width: 0,
                                height: 0
                            };
                            if (process.env.NODE_ENV !== 'production' && this.internalDebug) {
                                style.width = '1px';
                                style.height = '1px';
                                style.background = 'red';
                            }
                            return vue_1.h("div", { style: style, ref: "cursorRef" });
                        }
                    }),
                    vue_1.h(vueuc_1.VFollower, { ref: "followerRef", placement: "bottom-start", show: this.showMenu, containerClass: this.namespace, to: this.adjustedTo, teleportDisabled: this.adjustedTo === _utils_1.useAdjustedTo.tdkey }, {
                        default: () => (vue_1.h(vue_1.Transition, { name: "fade-in-scale-up-transition", appear: this.isMounted }, {
                            default: () => {
                                const { mergedTheme } = this;
                                return this.showMenu ? (vue_1.h(_internal_1.NInternalSelectMenu, { clsPrefix: mergedClsPrefix, theme: mergedTheme.peers.InternalSelectMenu, themeOverrides: mergedTheme.peerOverrides.InternalSelectMenu, autoPending: true, ref: "selectMenuInstRef", class: `${mergedClsPrefix}-mention-menu`, loading: this.loading, treeMate: this.treeMate, virtualScroll: false, style: this.cssVars, onMenuToggleOption: this.handleSelect, renderLabel: this.renderLabel }, $slots)) : null;
                            }
                        }))
                    })
                ]
            })));
    }
});
