/**
 * Copyright IBM Corp. 2026
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import "../../globals/settings.js";
import { forEach } from "../../globals/internal/collection-helpers.js";
import { carbonElement } from "../../globals/decorators/carbon-element.js";
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.126.0/helpers/decorate.js";
import iconLoader from "../../globals/internal/icon-loader.js";
import { DROPDOWN_DIRECTION, DROPDOWN_KEYBOARD_ACTION, DROPDOWN_SIZE } from "../dropdown/defs.js";
import dropdown_default from "../dropdown/dropdown.js";
import combo_box_default$1 from "./combo-box.scss.js";
import if_non_empty_default from "../../globals/directives/if-non-empty.js";
import spread from "../../globals/directives/spread.js";
import { html } from "lit";
import { property, query } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
import Close16 from "@carbon/icons/es/close/16.js";
//#region src/components/combo-box/combo-box.ts
/**
* Copyright IBM Corp. 2019, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Combo box.
*
* @element cds-custom-combo-box
* @fires cds-custom-combo-box-beingselected
*   The custom event fired before a combo box item is selected upon a user gesture.
*   Cancellation of this event stops changing the user-initiated selection.
* @fires cds-custom-combo-box-beingtoggled
*   The custom event fired before the open state of this combo box is toggled upon a user gesture.
*   Cancellation of this event stops the user-initiated toggling.
* @fires cds-custom-combo-box-selected - The custom event fired after a combo box item is selected upon a user gesture.
* @fires cds-custom-combo-box-toggled - The custom event fired after the open state of this combo box is toggled upon a user gesture.
*/
let CDSComboBox = class CDSComboBox extends dropdown_default {
	constructor(..._args) {
		super(..._args);
		this._filterInputValue = "";
		this._shouldTriggerBeFocusable = false;
		this.clearSelectionLabel = "Clear selection";
		this.inputLabel = "";
		this.shouldFilterItem = false;
		this.typeahead = false;
		this.allowCustomValue = false;
	}
	/**
	* @param item A combo box item.
	* @returns `true` if the given combo box item matches the query text user types.
	*/
	_testItemWithQueryText(item) {
		return (this.itemMatches || this._defaultItemMatches)(item, this._filterInputNode.value);
	}
	/**
	* The default item matching callback.
	*
	* @param item The combo box item.
	* @param queryText The query text user types.
	* @returns `true` if the given combo box item matches the given query text.
	*/
	_defaultItemMatches(item, queryText) {
		return item.textContent.toLowerCase().indexOf(queryText.toLowerCase()) >= 0;
	}
	connectedCallback() {
		super.connectedCallback();
		if (this.typeahead) {
			this.shouldFilterItem = true;
			this.setAttribute("should-filter-item", "");
		}
	}
	/**
	* Handles `input` event on the `<input>` for filtering.
	*/
	_handleInput(event) {
		const rawQueryText = this._filterInputNode.value;
		const queryText = rawQueryText.trim().toLowerCase();
		if (rawQueryText.length !== 0) this.setAttribute("isClosable", "");
		else this.removeAttribute("isClosable");
		const items = this.querySelectorAll(this.constructor.selectorItem);
		const firstMatchIndex = this._filterItems(items, queryText, rawQueryText);
		if (firstMatchIndex !== -1) {
			const highlightedItem = items[firstMatchIndex];
			if (highlightedItem) this._scrollItemIntoView(highlightedItem);
			if (this.typeahead && event?.inputType?.startsWith("insert")) {
				const suggestedItem = highlightedItem.textContent?.trim() ?? "";
				if (suggestedItem.toLowerCase().startsWith(rawQueryText.toLowerCase()) && suggestedItem.length > rawQueryText.length) {
					const suggestionText = rawQueryText + suggestedItem.slice(rawQueryText.length);
					this._filterInputNode.value = suggestionText;
					this._filterInputNode.setSelectionRange(rawQueryText.length, suggestionText.length);
					this._filterInputValue = suggestionText;
					this.open = true;
					this.requestUpdate();
					return;
				}
			}
		}
		this._filterInputValue = rawQueryText;
		if (this.allowCustomValue) {
			const previousValue = this.value;
			this.value = rawQueryText;
			if (previousValue !== this.value) this.dispatchEvent(new CustomEvent(this.constructor.eventSelect, {
				bubbles: true,
				composed: true,
				detail: {
					item: null,
					value: this.value
				}
			}));
		}
		this.open = true;
		this.requestUpdate();
	}
	_removeAutoCompleteSuggestion() {
		if (!this._filterInputNode) return;
		const { selectionStart, selectionEnd, value } = this._filterInputNode;
		if (selectionStart && selectionEnd && selectionEnd > selectionStart) {
			const cleanInput = value.slice(0, selectionStart);
			this._filterInputNode.value = cleanInput;
			this._filterInputNode.setSelectionRange(cleanInput.length, cleanInput.length);
			return;
		}
	}
	_filterItems(items, queryText, rawQueryText) {
		let firstMatchIndex = -1;
		const hasQuery = Boolean(queryText);
		forEach(items, (item, i) => {
			const comboItem = item;
			const index = i ?? -1;
			if (!hasQuery) {
				comboItem.style.display = "";
				comboItem.highlighted = false;
				return;
			}
			const matches = this.typeahead ? (comboItem.textContent || "").toLowerCase().startsWith(queryText) : (comboItem.textContent || "").toLowerCase().includes(queryText);
			const filterFunction = typeof this.shouldFilterItem === "function" ? this.shouldFilterItem : null;
			const shouldApplyBuiltInFilter = filterFunction === null && hasQuery && this.shouldFilterItem === true;
			const itemToString = (value) => value.textContent || "";
			const filterInputValue = rawQueryText.length === 0 ? null : rawQueryText;
			const passesFilter = filterFunction ? filterFunction({
				item: comboItem,
				itemToString,
				inputValue: filterInputValue
			}) : shouldApplyBuiltInFilter ? matches : true;
			if ((filterFunction !== null ? passesFilter : matches) && firstMatchIndex === -1) firstMatchIndex = index;
			if (filterFunction || shouldApplyBuiltInFilter) comboItem.style.display = passesFilter ? "" : "none";
			else comboItem.style.display = "";
			comboItem.highlighted = index === firstMatchIndex;
		});
		return firstMatchIndex;
	}
	_scrollItemIntoView(item) {
		if (!this._itemMenu) return;
		const menuRect = this._itemMenu.getBoundingClientRect();
		const itemRect = item.getBoundingClientRect();
		if (!menuRect || !itemRect) return;
		const menuBottom = menuRect.top + this._itemMenu.clientHeight;
		if (menuRect.top <= itemRect.top && itemRect.bottom <= menuBottom) return;
		const scrollTop = itemRect.top - menuRect.top;
		const scrollBottom = itemRect.bottom - menuRect.bottom;
		this._itemMenu.scrollTop += Math.abs(scrollTop) < Math.abs(scrollBottom) ? scrollTop : scrollBottom;
	}
	_getSelectedItem() {
		if (!this.value) return null;
		return Array.from(this.querySelectorAll(this.constructor.selectorItem)).find((it) => String(it.value) === String(this.value)) ?? null;
	}
	_revertInputToSelected(focus = true) {
		const selected = this._getSelectedItem();
		let text = selected?.textContent ?? "";
		if (this.allowCustomValue && !selected && this.value) text = this.value;
		this._filterInputValue = text;
		if (this._filterInputNode) {
			this._filterInputNode.value = text;
			if (focus) try {
				this._filterInputNode.focus();
				const len = text.length;
				this._filterInputNode.setSelectionRange(len, len);
			} catch {}
		}
		this._resetFilteredItems();
		this.removeAttribute("isClosable");
		this.requestUpdate();
	}
	_handleInputKeydown(event) {
		if (this.typeahead && (event.key === "ArrowDown" || event.key === "ArrowUp")) this._removeAutoCompleteSuggestion();
		if (event.key !== "Escape") return;
		if (!this._filterInputNode) return;
		if (this.open) this._handleUserInitiatedToggle(false);
		else {
			if (this.value) this._revertInputToSelected(true);
			else if (this._filterInputNode.value) this._clearInputWithoutSelecting(true);
			if (this.value || this._filterInputNode.value) this._handleUserInitiatedClearInput();
		}
	}
	_handleClickInner(event) {
		const { target } = event;
		if (this._selectionButtonNode?.contains(target)) this._handleUserInitiatedClearInput();
		else super._handleClickInner(event);
	}
	_handleKeypressInner(event) {
		const { key } = event;
		const action = this.constructor.getAction(key);
		const { TRIGGERING } = DROPDOWN_KEYBOARD_ACTION;
		if (this._selectionButtonNode?.contains(event.target) && (action === TRIGGERING || key === " ")) this._handleUserInitiatedClearInput();
		else super._handleKeypressInner(event);
	}
	/**
	* Handles user-initiated clearing the `<input>` for filtering.
	*/
	_handleUserInitiatedClearInput() {
		this._handleUserInitiatedSelectItem();
		if (this.value) this._revertInputToSelected(true);
		else this._clearInputWithoutSelecting(true);
		this.requestUpdate();
	}
	_handleUserInitiatedSelectItem(item) {
		if (item && !this._selectionShouldChange(item)) {
			this._filterInputValue = item.textContent || "";
			this.open = false;
			this.requestUpdate();
		}
		super._handleUserInitiatedSelectItem(item);
	}
	_selectionDidChange(itemToSelect) {
		this.value = !itemToSelect ? "" : itemToSelect.value;
		forEach(this.querySelectorAll(this.constructor.selectorItemSelected), (item) => {
			item.selected = false;
			item.setAttribute("aria-selected", "false");
		});
		if (itemToSelect) {
			itemToSelect.selected = true;
			itemToSelect.setAttribute("aria-selected", "true");
		}
		this._handleUserInitiatedToggle(false);
		if (this._filterInputNode) try {
			this._filterInputNode.focus();
			const val = this._filterInputNode.value || "";
			this._filterInputNode.setSelectionRange(val.length, val.length);
		} catch {}
	}
	_renderLabel() {
		const { disabled, inputLabel, label, open, readOnly, value, inputProps, _activeDescendant: activeDescendant, _filterInputValue: filterInputValue, _handleInput: handleInput, _handleInputKeydown: handleInputKeydown } = this;
		const inputClasses = classMap({
			[`cds-custom--text-input`]: true,
			[`cds-custom--text-input--empty`]: !value
		});
		let activeDescendantFallback;
		if (open && !activeDescendant) {
			const constructor = this.constructor;
			activeDescendantFallback = this.querySelectorAll(constructor.selectorItem)[0]?.id;
		}
		return html`
      <input
        id="trigger-button"
        class="${inputClasses}"
        ?disabled=${disabled}
        placeholder="${label}"
        .value=${filterInputValue}
        role="combobox"
        aria-label="${if_non_empty_default(inputLabel)}"
        aria-labelledby="dropdown-label"
        aria-controls="menu-body"
        aria-haspopup="listbox"
        aria-autocomplete="list"
        aria-expanded="${String(open)}"
        aria-activedescendant="${ifDefined(open ? activeDescendant ?? activeDescendantFallback : "")}"
        ?readonly=${readOnly}
        @input=${handleInput}
        @keydown=${handleInputKeydown}
        ...="${spread(this._normalizeInputProps(inputProps))}" />
    `;
	}
	_renderFollowingLabel() {
		const { clearSelectionLabel, _filterInputValue: filterInputValue } = this;
		if (filterInputValue.length != 0) this.setAttribute("isClosable", "");
		else this.removeAttribute("isClosable");
		return filterInputValue.length === 0 ? void 0 : html`
          <div
            id="selection-button"
            role="button"
            class="${"cds-custom"}--list-box__selection"
            tabindex="-1"
            title="${clearSelectionLabel}">
            ${iconLoader(Close16, { "aria-label": clearSelectionLabel })}
          </div>
        `;
	}
	_renderTitleLabel() {
		const { disabled, hideLabel, titleText, _slotTitleTextNode: slotTitleTextNode, _handleSlotchangeLabelText: handleSlotchangeLabelText } = this;
		return html`
      <label
        id="dropdown-label"
        part="title-text"
        class="${classMap({
			[`cds-custom--label`]: true,
			[`cds-custom--label--disabled`]: disabled,
			[`cds-custom--visually-hidden`]: hideLabel
		})}"
        ?hidden="${!(titleText || slotTitleTextNode && slotTitleTextNode.assignedNodes().length > 0)}">
        <slot name="title-text" @slotchange="${handleSlotchangeLabelText}"
          >${titleText}</slot
        >
      </label>
    `;
	}
	_normalizeInputProps(inputProps) {
		const normalizedInputProps = {};
		Object.entries(inputProps ?? {}).forEach(([key, value]) => {
			if (value === void 0 || value === null || value === false) return;
			normalizedInputProps[key] = value === true ? "" : String(value);
		});
		return normalizedInputProps;
	}
	shouldUpdate(changedProperties) {
		super.shouldUpdate(changedProperties);
		if (!changedProperties.has("value")) return true;
		if (this._selectedItemContent) {
			this._filterInputValue = this._selectedItemContent.textContent || "";
			return true;
		}
		if (this.allowCustomValue && this.value) {
			this._filterInputValue = String(this.value);
			return true;
		}
		if (this.value === "") this._filterInputValue = "";
		return true;
	}
	_clearInputWithoutSelecting(focus = true) {
		this._filterInputValue = "";
		if (this._filterInputNode) {
			this._filterInputNode.value = "";
			if (focus) try {
				this._filterInputNode.focus();
				this._filterInputNode.setSelectionRange(0, 0);
			} catch {}
		}
		this._resetFilteredItems();
		this.removeAttribute("isClosable");
		this.requestUpdate();
	}
	updated(changedProperties) {
		super.updated(changedProperties);
		if (changedProperties.has("open")) {
			if (this.open && this._filterInputNode) this._handleInput(changedProperties);
			else if (!this.open) {
				this._removeAutoCompleteSuggestion();
				this._resetFilteredItems();
				if (this._filterInputNode.value == "") this.value = "";
				if (this.value) {
					this._revertInputToSelected(false);
					if (this._filterInputNode && document.activeElement === this._filterInputNode) this._filterInputNode.blur();
				} else if (this._filterInputValue && this._filterInputValue.length > 0 && !this.allowCustomValue) {
					this._clearInputWithoutSelecting(false);
					if (this._filterInputNode && document.activeElement === this._filterInputNode) this._filterInputNode.blur();
				}
			}
		}
		const { _listBoxNode: listBoxNode } = this;
		if (listBoxNode) listBoxNode.classList.add(`cds-custom--combo-box`);
	}
	_resetFilteredItems() {
		forEach(this.querySelectorAll(this.constructor.selectorItem), (item) => {
			const comboItem = item;
			comboItem.style.display = "";
			comboItem.highlighted = false;
		});
	}
	static {
		this.TRIGGER_KEYS = new Set(["Enter"]);
	}
	/**
	* A selector that will return highlighted items.
	*/
	static get selectorItemHighlighted() {
		return `cds-custom-combo-box-item[highlighted]`;
	}
	/**
	* A selector that will return combo box items.
	*/
	static get selectorItem() {
		return `cds-custom-combo-box-item`;
	}
	/**
	* A selector that will return selected items.
	*/
	static get selectorItemSelected() {
		return `cds-custom-combo-box-item[selected]`;
	}
	/**
	* The name of the custom event fired before this combo box item is being toggled upon a user gesture.
	* Cancellation of this event stops the user-initiated action of toggling this combo box item.
	*/
	static get eventBeforeToggle() {
		return `cds-custom-combo-box-beingtoggled`;
	}
	/**
	* The name of the custom event fired after this combo box item is toggled upon a user gesture.
	*/
	static get eventToggle() {
		return `cds-custom-combo-box-toggled`;
	}
	/**
	* The name of the custom event fired before a combo box item is selected upon a user gesture.
	* Cancellation of this event stops changing the user-initiated selection.
	*/
	static get eventBeforeSelect() {
		return `cds-custom-combo-box-beingselected`;
	}
	/**
	* The name of the custom event fired after a a combo box item is selected upon a user gesture.
	*/
	static get eventSelect() {
		return `cds-custom-combo-box-selected`;
	}
	static {
		this.styles = combo_box_default$1;
	}
};
__decorate([query("input")], CDSComboBox.prototype, "_filterInputNode", void 0);
__decorate([query("#menu-body")], CDSComboBox.prototype, "_itemMenu", void 0);
__decorate([query("#selection-button")], CDSComboBox.prototype, "_selectionButtonNode", void 0);
__decorate([property({ attribute: "clear-selection-label" })], CDSComboBox.prototype, "clearSelectionLabel", void 0);
__decorate([property({ attribute: "input-label" })], CDSComboBox.prototype, "inputLabel", void 0);
__decorate([property({ attribute: false })], CDSComboBox.prototype, "itemMatches", void 0);
__decorate([property({
	attribute: "should-filter-item",
	converter: { fromAttribute: (value) => value !== null }
})], CDSComboBox.prototype, "shouldFilterItem", void 0);
__decorate([property({
	type: Boolean,
	reflect: true
})], CDSComboBox.prototype, "typeahead", void 0);
__decorate([property({
	type: Boolean,
	attribute: "allow-custom-value"
})], CDSComboBox.prototype, "allowCustomValue", void 0);
__decorate([property({
	type: Object,
	attribute: false
})], CDSComboBox.prototype, "inputProps", void 0);
CDSComboBox = __decorate([carbonElement(`cds-custom-combo-box`)], CDSComboBox);
var combo_box_default = CDSComboBox;
//#endregion
export { DROPDOWN_DIRECTION, DROPDOWN_SIZE, combo_box_default as default };

//# sourceMappingURL=combo-box.js.map