import { keyEnter, keyEscape, keyArrowDown, keyArrowUp, keySpace, keyTab } from '@ni/fast-web-utilities';
import { listOptionTag } from '../../list-option';
import { processUpdates, waitForUpdatesAsync } from '../../testing/async-helpers';
import { FilterMode } from '../types';
/**
 * Page object for the `nimble-select` component to provide consistent ways
 * of querying and interacting with the component during tests.
 */
export class SelectPageObject {
    constructor(selectElement) {
        this.selectElement = selectElement;
    }
    async openAndSetFilterText(filterText) {
        if (this.selectElement.filterMode === FilterMode.none) {
            throw new Error('Can not set filter text with filterMode set to "none".');
        }
        this.clickSelect();
        const filterInput = this.getFilterInput();
        if (filterInput) {
            filterInput.value = filterText;
        }
        await waitForUpdatesAsync();
        const inputEvent = new InputEvent('input');
        filterInput?.dispatchEvent(inputEvent);
        await waitForUpdatesAsync();
    }
    async closeDropdown() {
        this.selectElement.open = false;
        await waitForUpdatesAsync();
    }
    async setOptions(options) {
        options.forEach(option => {
            option.setAttribute('role', 'option');
        });
        this.selectElement.slottedOptions = options;
        await waitForUpdatesAsync();
    }
    getFilteredOptions() {
        return this.selectElement.filteredOptions;
    }
    getSelectedOption() {
        return this.selectElement.selectedOptions[0] ?? null;
    }
    getActiveOption() {
        return (this.selectElement.options.find(o => o.activeOption) ?? null);
    }
    getDisplayText() {
        const displayText = this.selectElement.shadowRoot?.querySelector('.selected-value')
            ?.textContent ?? '';
        return displayText.trim();
    }
    getGroupLabels() {
        return Array.from(this.selectElement.querySelectorAll('[role="group"]') ?? []).map(group => group.labelContent);
    }
    getGroupOptionLabelsByIndex(groupIndex) {
        const group = Array.from(this.selectElement.querySelectorAll('[role="group"]') ?? [])[groupIndex];
        return Array.from(group?.querySelectorAll('[role="option"]') ?? []).map(option => option.textContent?.trim() ?? '');
    }
    getGroupOptionLabelsByLabel(groupLabel) {
        const group = Array.from(this.selectElement.querySelectorAll('[role="group"]') ?? []).find(g => g.labelContent === groupLabel);
        return Array.from(group?.querySelectorAll('[role="option"]') ?? []).map(option => option.textContent?.trim() ?? '');
    }
    /**
     * Gets the label text of the element
     * @returns The current slotted label text, if any
     */
    getLabelText() {
        return this.selectElement.labelContent;
    }
    /**
     * Either opens or closes the dropdown depending on its current state
     */
    clickSelect() {
        this.selectElement.click();
    }
    clickActiveItem() {
        if (!this.selectElement.open) {
            throw new Error('Select must be open to click selectedItem');
        }
        const selectedOption = this.getActiveOption();
        if (!selectedOption) {
            throw new Error('No option is selected to click');
        }
        const visibleOptions = this.getVisibleOptions();
        this.clickOption(visibleOptions.indexOf(selectedOption));
    }
    async clickFilterInput() {
        if (!this.selectElement.filterInput) {
            throw new Error('Filter input is not available.');
        }
        this.selectElement.filterInput.click();
        await waitForUpdatesAsync();
    }
    /**
     * Selects an option using an index value into the list of visible options.
     * Options that have the `hidden` or `visuallyHidden` attribute set to true
     * are not considered visible.
     * @param index The index of the option in the visible set to be selected
     * @remarks Prefer clickOptionWithDisplayText where possible. This method is
     * useful when the display text is not unique and the index is known.
     */
    clickOption(index) {
        const visibleOptions = this.getVisibleOptions();
        if (index >= visibleOptions.length) {
            throw new Error('"index" greater than number of current displayed options');
        }
        const option = visibleOptions[index];
        option.scrollIntoView();
        option.click();
    }
    /**
     * Click the option with the text provided by the 'displayText' parameter.
     * @param value The text of the option to be selected
     * @remarks This method is useful when the display text is unique. If the
     * display text is not unique, the first option with the matching text will
     * be selected.
     */
    clickOptionWithDisplayText(displayText) {
        if (!this.selectElement.open) {
            this.clickSelect();
        }
        const optionIndex = this.getVisibleOptions().findIndex(o => o.text === displayText);
        if (optionIndex === -1) {
            throw new Error(`No option with "text" of ${displayText}`);
        }
        this.clickOption(optionIndex);
    }
    clickClearButton() {
        if (!this.selectElement.clearable) {
            throw new Error('Select must set "clearable" in order to click clear button');
        }
        if (this.selectElement.selectedIndex === -1
            || this.selectElement.displayPlaceholder) {
            throw new Error('Select must have a selected element in order to click clear button');
        }
        const clearButton = this.getClearButton();
        clearButton?.click();
        processUpdates();
    }
    async clickAway() {
        this.selectElement.dispatchEvent(new Event('focusout'));
        await waitForUpdatesAsync();
    }
    pressEnterKey() {
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: keyEnter }));
    }
    pressEscapeKey() {
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: keyEscape }));
    }
    pressTabKey() {
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: keyTab }));
        this.selectElement.dispatchEvent(new FocusEvent('focusout'));
    }
    pressArrowDownKey() {
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: keyArrowDown }));
    }
    pressArrowUpKey() {
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: keyArrowUp }));
    }
    pressCharacterKey(character) {
        if (character.length !== 1) {
            throw new Error('character parameter must contain only a single character');
        }
        if (this.selectElement.open
            && this.selectElement.filterMode !== FilterMode.none) {
            const filterInput = this.selectElement.filterInput;
            filterInput.value += character;
        }
        const inputElement = this.selectElement.open
            && this.selectElement.filterMode !== FilterMode.none
            ? this.selectElement.filterInput
            : this.selectElement;
        inputElement.dispatchEvent(new InputEvent('input'));
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: character }));
    }
    async pressSpaceKey() {
        const alreadyOpen = this.selectElement.open;
        this.selectElement.dispatchEvent(new KeyboardEvent('keydown', { key: keySpace, bubbles: true }));
        await waitForUpdatesAsync();
        if (this.selectElement.filterMode === FilterMode.standard
            && alreadyOpen) {
            // add space to end of current filter
            const filterValue = `${this.selectElement.filterInput?.value ?? ''} `;
            if (this.selectElement.filterInput) {
                this.selectElement.filterInput.value = filterValue;
            }
            this.selectElement.filterInput?.dispatchEvent(new InputEvent('input', { inputType: 'insertText' }));
        }
        await waitForUpdatesAsync();
    }
    /**
     * This method will set the filter input value to the provided text, which
     * will result in the synchronous dispatch of the 'filter-input' event. The
     * dropdown must be open for this method to work.
     * @param filterText
     */
    setFilter(filterText) {
        if (this.selectElement.filterMode === FilterMode.none) {
            throw new Error('Select has filterMode of "none" so there is no filter input');
        }
        if (!this.selectElement.open) {
            throw new Error('Select must be open to set filter text');
        }
        const filterInput = this.getFilterInput();
        filterInput.value = filterText;
        const inputEvent = new InputEvent('input');
        filterInput?.dispatchEvent(inputEvent);
    }
    /**
     * This method will clear any present filter text, which will result in the
     * synchronous dispatch of the 'filter-input' event. The dropdown must be
     * open for this method to work.
     */
    clearFilter() {
        if (this.selectElement.filterMode === FilterMode.none) {
            throw new Error('Select has filterMode of "none" so there is no filter input');
        }
        const filterInput = this.getFilterInput();
        filterInput.value = '';
        filterInput?.dispatchEvent(new InputEvent('input', { inputType: 'deleteContentBackward' }));
    }
    isDropdownVisible() {
        return (this.selectElement.shadowRoot?.querySelector('.listbox') !== null);
    }
    isClearButtonVisible() {
        return this.getClearButton() != null;
    }
    isFilterInputVisible() {
        return (this.selectElement.shadowRoot?.querySelector('.filter-field')
            !== null);
    }
    isOptionVisible(index) {
        if (index >= this.selectElement.options.length) {
            throw new Error('Indexing past number of options');
        }
        const option = this.selectElement.options[index];
        const optionRects = option.getClientRects();
        return optionRects.length > 0 && optionRects[0].height !== 0;
    }
    isNoResultsLabelVisible() {
        return (this.selectElement.shadowRoot?.querySelector('.no-results-label') !== null);
    }
    isLoadingVisualVisible() {
        return (this.selectElement.shadowRoot?.querySelector('.loading-container') !== null);
    }
    getFilterInputText() {
        return this.selectElement.filterInput?.value ?? '';
    }
    getVisibleOptions() {
        const options = Array.from(this.selectElement.querySelectorAll(listOptionTag));
        return options.filter(o => !(o.hidden || o.visuallyHidden));
    }
    getFilterInput() {
        if (this.selectElement.filterMode === FilterMode.none) {
            throw new Error('Select has filterMode of "none" so there is no filter input');
        }
        return this.selectElement.filterInput;
    }
    getClearButton() {
        return this.selectElement.shadowRoot?.querySelector('.clear-button');
    }
}
//# sourceMappingURL=select.pageobject.js.map