import { __decorate } from "tslib";
import { Observable, observable } from '@ni/fast-element';
import { DesignSystem, ListboxElement as FoundationListbox, ListboxOption } from '@ni/fast-foundation';
import { keyEnter, keyEscape, keyTab } from '@ni/fast-web-utilities';
import { styles } from './styles';
import { template } from './template';
import { diacriticInsensitiveStringNormalizer } from '../../utilities/models/string-normalizers';
/**
 * A rich text mention listbox which acts as a popup for "@mention" support in editor
 */
export class RichTextMentionListbox extends FoundationListbox {
    constructor() {
        super(...arguments);
        /**
         * The space available in the viewport for the listbox when opened.
         *
         * @internal
         */
        this.availableViewportHeight = 0;
        /**
         * @internal
         */
        this.filter = '';
        /**
         * The collection of currently filtered options.
         * The approach is defined based on the `Combobox.filteredOptions` implementation.
         *
         * @internal
         */
        this.filteredOptions = [];
        this.anchorElementIntersectionObserver = new IntersectionObserver(entries => {
            if (!entries[0]?.isIntersecting) {
                this.setOpen(false);
            }
        }, { threshold: 1.0, root: document });
    }
    /**
     * @public
     */
    close() {
        this.setOpen(false);
    }
    /**
     * The list of options.
     *
     * @public
     * @remarks
     * Overrides `Listbox.options`.
     */
    get options() {
        Observable.track(this, 'options');
        return this.filteredOptions?.length > 0 ? this.filteredOptions : [];
    }
    set options(value) {
        this._options = value;
        Observable.notify(this, 'options');
    }
    /**
     * Triggers when the mention plugin is activated upon pressing the `key`
     *
     * @public
     */
    show(options) {
        const listboxTop = options.anchorNode.getBoundingClientRect().bottom;
        this.availableViewportHeight = Math.trunc(window.innerHeight - listboxTop);
        this.filter = options.filter;
        this.anchorElement = options.anchorNode;
        this.setOpen(true);
        this.filterOptions();
    }
    /**
     * Handle keydown actions for listbox navigation and selection.
     *
     * @param e - the keyboard event
     * @public
     */
    keydownHandler(event) {
        if (!this.open) {
            return false;
        }
        switch (event.key) {
            case keyTab:
            case keyEnter: {
                if (!this.hasSelectableOptions) {
                    return false;
                }
                const mentionDetail = {
                    href: this.firstSelectedOption.value,
                    displayName: this.firstSelectedOption.text
                };
                this.$emit('mention-selected', mentionDetail);
                this.setOpen(false);
                return true;
            }
            case keyEscape: {
                this.setOpen(false);
                return false;
            }
            default: {
                super.keydownHandler(event);
                return false;
            }
        }
    }
    /**
     * Filter available options by filter value.
     * The method is defined based on the `Combobox.filterOptions` and `Combobox.inputHandler` implementation.
     *
     * @internal
     */
    filterOptions() {
        if (!this.filter) {
            this.filteredOptions = this._options;
        }
        else {
            const normalizedFilter = diacriticInsensitiveStringNormalizer(this.filter);
            this.filteredOptions = this._options.filter(o => diacriticInsensitiveStringNormalizer(o.text).includes(normalizedFilter));
        }
        this._options.forEach(o => {
            o.hidden = !this.filteredOptions.includes(o);
        });
        if (this.filteredOptions.length > 0) {
            this.selectedOptions = [this.filteredOptions[0]];
            this.selectedIndex = this.options.indexOf(this.firstSelectedOption);
        }
        else {
            this.selectedOptions = [];
            this.selectedIndex = -1;
        }
    }
    /**
     * Synchronize the form-associated proxy and update the value property of the element.
     *
     * @param prev - the previous collection of slotted option elements
     * @param next - the next collection of slotted option elements
     *
     * @internal
     */
    slottedOptionsChanged(prev, next) {
        super.slottedOptionsChanged(prev, next);
        this.filterOptions();
    }
    /**
     * Triggers the `suggestionProps` command to notify the tiptap editor to select the option.
     * The method is defined based on the `Listbox.clickHandler` implementation.
     *
     * @internal
     */
    clickHandler(e) {
        const capturedElement = e.target.closest('option,[role=option]');
        const capturedListOption = capturedElement;
        if (!capturedListOption || capturedListOption.disabled) {
            return false;
        }
        const mentionDetail = {
            href: capturedListOption.value,
            displayName: capturedListOption.text
        };
        this.$emit('mention-selected', mentionDetail);
        this.setOpen(false);
        return true;
    }
    /**
     * Observes the anchor element using intersection observer.
     * Once the anchor element intersects, the anchor region will be closed.
     *
     * @internal
     */
    anchorElementChanged(prev, next) {
        if (prev) {
            this.anchorElementIntersectionObserver.unobserve(prev);
        }
        if (this.region && this.anchorElement) {
            this.region.anchorElement = this.anchorElement;
            this.region.update();
            this.anchorElementIntersectionObserver.observe(next);
        }
    }
    /**
     * Observes the anchor region.
     *
     * @internal
     */
    regionChanged() {
        if (this.regionNotifier) {
            this.regionNotifier.unsubscribe(this);
        }
        this.regionNotifier = Observable.getNotifier(this.region);
        this.regionNotifier.subscribe(this);
    }
    /**
     * Handles the events of the anchored region.
     * Repositions the listbox scroll bar when the `initialLayoutComplete` event is triggered.
     * Other events will be passed to the base class.
     *
     * @internal
     */
    handleChange(source, args) {
        super.handleChange(source, args);
        if (args === 'initialLayoutComplete') {
            this.focusAndScrollOptionIntoView();
        }
    }
    /**
     * Focus the control and scroll the first selected option into view.
     *
     * @internal
     * @remarks
     * Overrides: `Listbox.focusAndScrollOptionIntoView`
     */
    focusAndScrollOptionIntoView() {
        if (this.open && this.firstSelectedOption !== null) {
            requestAnimationFrame(() => {
                this.firstSelectedOption?.scrollIntoView({ block: 'nearest' });
            });
        }
    }
    setOpen(value) {
        this.open = value;
    }
}
__decorate([
    observable
], RichTextMentionListbox.prototype, "open", void 0);
__decorate([
    observable
], RichTextMentionListbox.prototype, "region", void 0);
__decorate([
    observable
], RichTextMentionListbox.prototype, "availableViewportHeight", void 0);
__decorate([
    observable
], RichTextMentionListbox.prototype, "filteredOptions", void 0);
__decorate([
    observable
], RichTextMentionListbox.prototype, "anchorElement", void 0);
const nimbleRichTextMentionListbox = RichTextMentionListbox.compose({
    baseName: 'rich-text-mention-listbox',
    template,
    styles
});
DesignSystem.getOrCreate()
    .withPrefix('nimble')
    .register(nimbleRichTextMentionListbox());
export const richTextMentionListboxTag = 'nimble-rich-text-mention-listbox';
//# sourceMappingURL=index.js.map