import { __esDecorate, __runInitializers } from "tslib";
import { html, LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';
import { bound } from '../../../decorators/bound.js';
let Autocomplete = (() => {
    let _classSuper = LitElement;
    let _instanceExtraInitializers = [];
    let _inputId_decorators;
    let _inputId_initializers = [];
    let _inputId_extraInitializers = [];
    let _positionArea_decorators;
    let _positionArea_initializers = [];
    let _positionArea_extraInitializers = [];
    let _handleInputFocus_decorators;
    let _handleInput_decorators;
    let _handleKeydown_decorators;
    let _handleInputBlur_decorators;
    let _handleSuggestionSelect_decorators;
    let _handleSuggestionsSlotChange_decorators;
    return class Autocomplete extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _inputId_decorators = [state()];
            _positionArea_decorators = [property()];
            _handleInputFocus_decorators = [bound];
            _handleInput_decorators = [bound];
            _handleKeydown_decorators = [bound];
            _handleInputBlur_decorators = [bound];
            _handleSuggestionSelect_decorators = [bound];
            _handleSuggestionsSlotChange_decorators = [bound];
            __esDecorate(this, null, _inputId_decorators, { kind: "accessor", name: "inputId", static: false, private: false, access: { has: obj => "inputId" in obj, get: obj => obj.inputId, set: (obj, value) => { obj.inputId = value; } }, metadata: _metadata }, _inputId_initializers, _inputId_extraInitializers);
            __esDecorate(this, null, _positionArea_decorators, { kind: "accessor", name: "positionArea", static: false, private: false, access: { has: obj => "positionArea" in obj, get: obj => obj.positionArea, set: (obj, value) => { obj.positionArea = value; } }, metadata: _metadata }, _positionArea_initializers, _positionArea_extraInitializers);
            __esDecorate(this, null, _handleInputFocus_decorators, { kind: "method", name: "handleInputFocus", static: false, private: false, access: { has: obj => "handleInputFocus" in obj, get: obj => obj.handleInputFocus }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _handleInput_decorators, { kind: "method", name: "handleInput", static: false, private: false, access: { has: obj => "handleInput" in obj, get: obj => obj.handleInput }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _handleKeydown_decorators, { kind: "method", name: "handleKeydown", static: false, private: false, access: { has: obj => "handleKeydown" in obj, get: obj => obj.handleKeydown }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _handleInputBlur_decorators, { kind: "method", name: "handleInputBlur", static: false, private: false, access: { has: obj => "handleInputBlur" in obj, get: obj => obj.handleInputBlur }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _handleSuggestionSelect_decorators, { kind: "method", name: "handleSuggestionSelect", static: false, private: false, access: { has: obj => "handleSuggestionSelect" in obj, get: obj => obj.handleSuggestionSelect }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _handleSuggestionsSlotChange_decorators, { kind: "method", name: "handleSuggestionsSlotChange", static: false, private: false, access: { has: obj => "handleSuggestionsSlotChange" in obj, get: obj => obj.handleSuggestionsSlotChange }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        createRenderRoot() {
            return this;
        }
        /**
         * The MutationObserver instance used to watch for changes in slotted children.
         */
        observer = __runInitializers(this, _instanceExtraInitializers);
        #inputId_accessor_storage = __runInitializers(this, _inputId_initializers, ''
        /**
         * The position area for the suggestions popover.
         * This can be 'bottom' or 'top', depending on available space.
         * Default is 'bottom'.
         *
         * Note, this is set dynamically based on available space
         * and the position of the input element. This only sets the initial value.
         *
         * @attribute
         * @type {'bottom' | 'top'}
         * @default 'bottom'
         */
        );
        /**
         * The ID of the input element, generated if not provided.
         * This is used for CSS anchoring and to ensure unique IDs for accessibility.
         */
        get inputId() { return this.#inputId_accessor_storage; }
        set inputId(value) { this.#inputId_accessor_storage = value; }
        #positionArea_accessor_storage = (__runInitializers(this, _inputId_extraInitializers), __runInitializers(this, _positionArea_initializers, 'bottom'
        /**
         * The reference to the slotted input element.
         * This should be an `HTMLInputElement` or behave like one.
         */
        ));
        /**
         * The position area for the suggestions popover.
         * This can be 'bottom' or 'top', depending on available space.
         * Default is 'bottom'.
         *
         * Note, this is set dynamically based on available space
         * and the position of the input element. This only sets the initial value.
         *
         * @attribute
         * @type {'bottom' | 'top'}
         * @default 'bottom'
         */
        get positionArea() { return this.#positionArea_accessor_storage; }
        set positionArea(value) { this.#positionArea_accessor_storage = value; }
        /**
         * The reference to the slotted input element.
         * This should be an `HTMLInputElement` or behave like one.
         */
        inputRef = __runInitializers(this, _positionArea_extraInitializers);
        /**
         * The reference to the slotted suggestions element, which should be a `ui-listbox`.
         * This is used to manage the suggestions popover and filtering.
         */
        suggestionsRef;
        /**
         * Checks if the suggestions popover is currently open.
         */
        get opened() {
            return this.suggestionsRef?.matches(':popover-open') || false;
        }
        connectedCallback() {
            super.connectedCallback();
            this.observer = new MutationObserver(this.handleMutations.bind(this));
            this.observer.observe(this, { childList: true });
            if (!this.id) {
                this.id = `autocomplete-${Math.random().toString(36).substring(2, 15)}`;
            }
            this.classList.add(this.id);
        }
        disconnectedCallback() {
            super.disconnectedCallback();
            this.observer?.disconnect();
            this.observer = undefined;
            this.clearInputListeners();
            this.clearSuggestionsListeners();
        }
        firstUpdated(cp) {
            super.firstUpdated(cp);
            this.firstSetup();
        }
        /**
         * Performs initial setup after the first update, ensuring that slotted input
         * and suggestions elements are configured.
         */
        async firstSetup() {
            await this.updateComplete;
            const input = this.querySelector('[slot="input"]');
            const suggestions = this.querySelector('[slot="suggestions"]');
            if (input) {
                this.setupInput(input);
            }
            if (suggestions) {
                this.setupSuggestions(suggestions);
            }
        }
        /**
         * Handles mutations observed on the component's slotted children.
         * This is used to set up or tear down input and suggestions elements when they are added or removed.
         * @param mutations An array of MutationRecord objects.
         */
        handleMutations(mutations) {
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    for (const node of mutation.removedNodes) {
                        if (node instanceof HTMLElement) {
                            if (node.slot === 'input' && this.inputRef === node) {
                                this.clearInputListeners();
                            }
                            else if (node.slot === 'suggestions' && this.suggestionsRef === node) {
                                this.clearSuggestionsListeners();
                            }
                        }
                    }
                    for (const node of mutation.addedNodes) {
                        if (node instanceof HTMLElement) {
                            if (node.slot === 'input') {
                                this.setupInput(node);
                            }
                            else if (node.slot === 'suggestions') {
                                this.setupSuggestions(node);
                            }
                        }
                    }
                }
            }
        }
        /**
         * Clears event listeners from the current input reference and resets it.
         */
        clearInputListeners() {
            if (this.inputRef) {
                this.inputRef.removeEventListener('focus', this.handleInputFocus);
                this.inputRef.removeEventListener('input', this.handleInput);
                this.inputRef.removeEventListener('keydown', this.handleKeydown);
                this.inputRef.removeEventListener('blur', this.handleInputBlur);
            }
            this.inputRef = null;
        }
        /**
         * Clears event listeners from the current suggestions reference and resets it.
         */
        clearSuggestionsListeners() {
            if (this.suggestionsRef) {
                this.suggestionsRef.removeEventListener('select', this.handleSuggestionSelect);
                // If ui-listbox uses a slot for its items, listen to its slotchange
                this.suggestionsRef.removeEventListener('itemschange', this.handleSuggestionsSlotChange);
                if (this.suggestionsRef.matches(':popover-open')) {
                    this.suggestionsRef.hidePopover();
                }
            }
            this.suggestionsRef = null;
        }
        /**
         * Sets up the slotted input element.
         * Assigns an ID if necessary, sets up CSS anchoring, and attaches event listeners.
         * @param input The HTMLElement to be used as the input.
         */
        setupInput(input) {
            this.clearInputListeners(); // Clear any old listeners
            if (!input.id) {
                this.inputId = `autocomplete-input-${Math.random().toString(36).substring(2, 15)}`;
                input.id = this.inputId;
            }
            else {
                this.inputId = input.id;
            }
            const anchorElement = this.querySelector('[slot="anchor"]');
            // Ensure CSS anchor positioning can work
            const anchor = anchorElement || input;
            anchor.style.setProperty('anchor-name', `--${this.inputId}`);
            this.inputRef = input; // Assuming it behaves like an input
            this.inputRef.addEventListener('focus', this.handleInputFocus);
            this.inputRef.addEventListener('input', this.handleInput);
            this.inputRef.addEventListener('keydown', this.handleKeydown);
            this.inputRef.addEventListener('blur', this.handleInputBlur);
        }
        /**
         * Sets up the slotted suggestions element (assumed to be a `UiListbox`).
         * Configures popover behavior, CSS anchoring, and attaches event listeners.
         * @param suggestionsElement The `UiListbox` element to be used for suggestions.
         */
        setupSuggestions(suggestionsElement) {
            this.clearSuggestionsListeners(); // Clear any old listeners
            this.suggestionsRef = suggestionsElement;
            this.suggestionsRef.popover = 'manual';
            this.suggestionsRef.tabIndex = -1; // Prevent direct focus
            this.suggestionsRef.addEventListener('select', this.handleSuggestionSelect);
            // The `List` dispatches `itemschange` when the slot changes, so we can listen to it.
            this.suggestionsRef.addEventListener('itemschange', this.handleSuggestionsSlotChange);
            // Initial filter
            this.filterSuggestions(this.inputRef ? this.inputRef.value : '');
        }
        /**
         * Handles the focus event on the input element.
         * Opens the suggestions popover if there are items to display.
         */
        handleInputFocus() {
            const items = this.suggestionsRef?.items;
            if (!items || items.length === 0) {
                // If no suggestions, do not open
                return;
            }
            const active = items.find((item) => !item.hidden);
            if (active) {
                this.openSuggestions();
                this.filterSuggestions(this.inputRef ? this.inputRef.value : '');
            }
        }
        /**
         * Handles the input event on the input element.
         * Filters suggestions based on the input query and opens/closes the popover accordingly.
         * @param event The input event.
         */
        handleInput(event) {
            this.openSuggestions();
            const query = event.target.value;
            this.filterSuggestions(query);
        }
        /**
         * Handles keydown events on the input element for navigating and selecting suggestions.
         * - ArrowDown/ArrowUp: Navigates the suggestion list.
         * - Enter: Selects the highlighted suggestion.
         * - Escape: Closes the suggestions popover.
         * @param event The keyboard event.
         */
        handleKeydown(event) {
            if (!this.suggestionsRef)
                return;
            const { key } = event;
            const isSuggestionsOpen = this.suggestionsRef.matches(':popover-open');
            if (['ArrowDown', 'ArrowUp'].includes(key)) {
                event.preventDefault();
                if (!isSuggestionsOpen) {
                    this.openSuggestions();
                    // Give popover a moment to open before trying to highlight
                    requestAnimationFrame(() => this.navigateList(key));
                }
                else {
                    this.navigateList(key);
                }
            }
            else if (key === 'Enter') {
                if (isSuggestionsOpen && this.suggestionsRef.highlightListItem) {
                    event.preventDefault();
                    this.suggestionsRef.notifySelect(this.suggestionsRef.highlightListItem);
                }
            }
            else if (key === 'Escape') {
                if (isSuggestionsOpen) {
                    event.preventDefault();
                    this.closeSuggestions();
                }
            }
        }
        /**
         * Navigates the suggestion list based on the pressed key.
         * @param key The key that was pressed (ArrowDown or ArrowUp).
         */
        navigateList(key) {
            if (!this.suggestionsRef)
                return;
            switch (key) {
                case 'ArrowDown':
                    this.suggestionsRef.highlightNext();
                    break;
                case 'ArrowUp':
                    this.suggestionsRef.highlightPrevious();
                    break;
                // Don't handle Home or End keys here, as they break the usability of the input.
            }
        }
        /**
         * Handles the blur event on the input element.
         * Closes the suggestions popover if focus moves outside the autocomplete component.
         */
        handleInputBlur() {
            // We use the manual popover mode, so we need to close suggestions
            // when the input loses focus, but only the current active element is not part of the suggestions.
            requestAnimationFrame(() => {
                if (!this.inputRef?.contains(document.activeElement) &&
                    (!this.suggestionsRef || !this.suggestionsRef.contains(document.activeElement))) {
                    this.closeSuggestions();
                }
            });
        }
        /**
         * Handles the `select` event dispatched by the `ui-listbox` when a suggestion is chosen.
         * Dispatches an `autocomplete` event and closes the popover.
         * @param event The custom event from `ui-listbox`.
         */
        handleSuggestionSelect(event) {
            event.stopPropagation();
            const selectedItem = event.detail.item;
            this.dispatchEvent(new CustomEvent('autocomplete', {
                detail: { item: selectedItem },
                bubbles: false,
                composed: false,
            }));
            this.closeSuggestions();
        }
        /**
         * Handles the `itemschange` event dispatched by the `ui-listbox` when its slotted items change.
         * Re-filters the suggestions.
         */
        handleSuggestionsSlotChange() {
            const value = this.inputRef ? this.inputRef.value : '';
            if (!value) {
                return;
            }
            this.openSuggestions();
            this.filterSuggestions(value);
        }
        /**
         * Opens the suggestions popover if it's not already open and there are visible items.
         */
        openSuggestions() {
            const popover = this.suggestionsRef;
            if (!popover || popover.matches(':popover-open')) {
                return;
            }
            // we need to open the popover first to make any measurements
            popover.showPopover();
            const anchor = this.querySelector('[slot="anchor"]') || this.inputRef;
            if (!anchor) {
                return;
            }
            this.positionArea = this.decidePositionArea(popover, anchor);
        }
        /**
         * Decides the position area for the popover based on available space.
         * It checks if there is enough space below or above the anchor element and decides accordingly.
         *
         * We need to do this because we set the popover height to `100%`, `-webkit-fill-available`, or `-moz-available`
         * and it makes the popover to always open at the bottom, even if there is no space. The `position-try-fallbacks`
         * will not work in this case, because from its perspective there is always enough space, even if that will cause
         * the popover to have a height of just a few pixels.
         *
         * @param popover The popover element to position.
         * @param anchor The anchor element relative to which the popover will be positioned.
         * @returns 'top' or 'bottom' based on available space.
         */
        decidePositionArea(popover, anchor) {
            let newArea = 'bottom';
            // Get bounding rectangles
            const anchorRect = anchor.getBoundingClientRect();
            const viewportHeight = window.innerHeight;
            const spaceBelow = viewportHeight - anchorRect.bottom;
            const spaceAbove = anchorRect.top;
            // Estimate a typical/minimum height the popover might need to be useful
            const popoverThresholdHeight = popover.scrollHeight || 150;
            if (spaceBelow < popoverThresholdHeight && spaceAbove > spaceBelow && spaceAbove > popoverThresholdHeight) {
                // Not enough space below, but more (and sufficient) space above
                newArea = 'top';
            }
            else if (spaceBelow < popoverThresholdHeight && spaceAbove > spaceBelow) {
                // Not enough space below, and space above is more than space below (even if not "sufficient")
                newArea = 'top';
            }
            else {
                newArea = 'bottom'; // Default to bottom if enough space or if top is worse
            }
            return newArea;
        }
        /**
         * Closes the suggestions popover if it's open and clears any highlighted item.
         */
        closeSuggestions() {
            if (this.suggestionsRef && this.suggestionsRef.matches(':popover-open')) {
                this.suggestionsRef.hidePopover();
            }
        }
        /**
         * Filters the suggestions based on the provided query.
         * Hides items that do not match and manages the highlighted item state.
         * @param query The search query string.
         */
        filterSuggestions(query) {
            if (!this.suggestionsRef) {
                return;
            }
            const lowerCaseQuery = query.toLowerCase().trim();
            let firstVisibleItem = null;
            // The `items` getter in `List.ts` (parent of UiListbox) correctly gets assigned elements.
            const items = this.suggestionsRef.items;
            for (const item of items) {
                let matches = false;
                if (lowerCaseQuery === '') {
                    matches = true;
                }
                else if (item.dataset.index) {
                    const indexFields = item.dataset.index.split(' ').filter(Boolean);
                    for (const field of indexFields) {
                        const valueToSearch = item.dataset[field] || '';
                        if (valueToSearch.toLowerCase().includes(lowerCaseQuery)) {
                            matches = true;
                            break;
                        }
                    }
                }
                else {
                    const valueToSearch = item.dataset.value || item.textContent || '';
                    matches = valueToSearch.toLowerCase().includes(lowerCaseQuery);
                }
                item.hidden = !matches;
                if (matches && !firstVisibleItem) {
                    firstVisibleItem = item;
                }
            }
            // If the currently highlighted item is now hidden, try to highlight the first visible one or clear.
            if (this.suggestionsRef.highlightListItem?.hidden) {
                // the highlightListItem clears the highlighted item if it is not passed an argument.
                this.suggestionsRef?.highlightItem(firstVisibleItem);
            }
            if (!firstVisibleItem) {
                // Close if no items are visible
                this.closeSuggestions();
            }
        }
        render() {
            const { id, positionArea, inputId } = this;
            /*
            position-try-fallbacks:
                      flip-block,
                      flip-inline,
                      flip-block flip-inline;
            */
            return html `
      <style>
        .${id} {
          display: inline-block;

          [popover] {
            border: none;
            margin: 0;

            box-shadow: var(--md-sys-elevation-1);
            border-radius: var(--md-sys-shape-corner-medium);

            position-area: ${positionArea};
            position-anchor: --${inputId};

            overflow: auto;
            /* We try 100% and then vendor options which are more accurate */
            height: 100%;
            height: -webkit-fill-available;
            height: -moz-available;
            max-height: max-content;
            width: anchor-size(width);
          }

          [popover]:not(:popover-open) {
            display: none;
          }
        }
      </style>
    `;
        }
    };
})();
/**
 * An accessible and performant autocomplete component that enhances a text input with a list of suggestions.
 *
 * The `autocomplete-input` component provides a flexible way to add autocomplete functionality
 * to any text input. It works by coordinating a slotted input element with a slotted `ui-listbox`
 * containing suggestions.
 *
 * Key Features:
 *
 * - **Popover Management**: Automatically shows and hides the suggestions popover based on input focus
 *   and user interaction.
 * - **Keyboard Navigation**: Allows users to navigate suggestions using ArrowUp, ArrowDown keys directly
 *   from the input. Enter selects a suggestion, and Escape closes the popover.
 *   The suggestion list itself never gains focus.
 * - **Dynamic Filtering**: Filters the list of suggestions as the user types into the input. Items not matching
 *   the query are hidden (not removed).
 *   - By default, filtering checks `item.dataset.value` and then `item.textContent`.
 *   - If a `ui-list-item` has a `data-index` attribute (e.g., `data-index="name email"`), filtering will search
 *     within the specified `data-*` attributes (e.g., `data-name`, `data-email`).
 * - **Mutation Awareness**: Reacts to changes in slotted suggestions, re-filtering them if necessary.
 * - **Event-Driven**: Notifies the application via an `autocomplete` event when a suggestion is selected.
 *   The component itself does not modify the input's value, giving the application author full control.
 *
 * ### Accessibility
 *
 * The `autocomplete-input` component is designed with accessibility at its core:
 * - **Keyboard Navigation**: Full keyboard support for navigating suggestions (ArrowUp, ArrowDown),
 *   selecting (Enter), and closing (Escape), all while focus remains on the input field.
 * - **ARIA Attributes**: While the `autocomplete-input` manages ARIA attributes related to the popover's
 *   state, the slotted `ui-listbox` is responsible for its internal ARIA roles and states
 *   (e.g., `role="listbox"`, `aria-activedescendant`).
 * - **Labeling Suggestions**: It is **crucial** for accessibility that the slotted `ui-listbox`
 *   has an `aria-label` attribute. This provides a descriptive name for the list of suggestions,
 *   which is announced by screen readers.
 *
 * The component uses CSS anchor positioning to place the suggestions popover relative to the input.
 * Ensure your `ui-listbox` is styled appropriately for popover display (e.g., `popover="manual"`, `position-anchor`).
 * The component will manage `showPopover()` and `hidePopover()` calls.
 *
 * @slot input - The input element that will be used for autocomplete. This element should be an `HTMLInputElement`
 *               or behave like one (have a `value` property and dispatch `input`, `focus`, `blur`, and `keydown`
 *               events).
 * @slot suggestions - The `ui-listbox` element containing `ui-list-item` elements as suggestions.
 * @slot anchor - An optional element that points to element that will be used as the anchor for the popover.
 *                This is useful if you want to position the suggestions relative to a different element than the input.
 *                If not provided, the input element will be used as the anchor.
 * @slot Any additional content that should be rendered inside the component.
 *
 * @fires autocomplete - Dispatched when a suggestion is selected by the user (e.g., via click or Enter key).
 * The `event.detail` object contains:
 *   - `item`: The selected `UiListItem` instance.
 *
 * @example
 * ```html
 * <autocomplete-input @autocomplete="${this.handleAutocompleteSelection}">
 *   <input slot="input" type="text" placeholder="Search fruits..." />
 *   <ui-listbox slot="suggestions">
 *     <ui-list-item data-value="apple">Apple</ui-list-item>
 *     <ui-list-item data-value="banana">Banana</ui-list-item>
 *     <ui-list-item data-value="cherry">Cherry</ui-list-item>
 *   </ui-listbox>
 *   <!-- With aria-label for accessibility -->
 *   <ui-listbox slot="suggestions" aria-label="Fruit suggestions">
 *     <ui-list-item data-value="apple">Apple</ui-list-item>
 *     <ui-list-item data-value="banana">Banana</ui-list-item>
 *     <ui-list-item data-value="cherry">Cherry</ui-list-item>
 *   </ui-listbox>
 * </autocomplete-input>
 *
 * <script>
 *   // In your component/script
 *   handleAutocompleteSelection(event) {
 *     const selectedItem = event.detail.item;
 *     const inputElement = this.shadowRoot.querySelector('input[slot="input"]');
 *     if (inputElement) {
 *       inputElement.value = selectedItem.dataset.value || selectedItem.textContent;
 *     }
 *     console.log('Selected:', selectedItem.dataset.value);
 *   }
 * </script>
 * ```
 *
 * @example Dynamic filtering with `data-index`
 * ```html
 * <autocomplete-input @autocomplete="${this.handleUserSelection}">
 *   <input slot="input" type="text" placeholder="Search users..." />
 *   <ui-listbox slot="suggestions" aria-label="User suggestions">
 *     <ui-list-item data-id="1" data-name="Alice Wonderland" data-email="alice@example.com" data-index="name email">
 *       Alice Wonderland
 *       <span slot="supporting-text">alice@example.com</span>
 *     </ui-list-item>
 *     <ui-list-item data-id="2" data-name="Bob The Builder" data-email="bob@example.com" data-index="name email">
 *       Bob The Builder
 *       <span slot="supporting-text">bob@example.com</span>
 *     </ui-list-item>
 *   </ui-listbox>
 * </autocomplete-input>
 *
 * <script>
 *   // In your component/script
 *   handleUserSelection(event) {
 *     const selectedItem = event.detail.item;
 *     const inputElement = this.shadowRoot.querySelector('input[slot="input"]');
 *     if (inputElement) {
 *       // You might want to display the name, but store the ID
 *       inputElement.value = selectedItem.dataset.name;
 *     }
 *     console.log('Selected user ID:', selectedItem.dataset.id);
 *   }
 * </script>
 * ```
 */
export default Autocomplete;
//# sourceMappingURL=autocomplete.js.map