// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { HTMLElementEventEmitter, on } from '@breadstone/mosaik-elements';
import { getSlot, getSlotElements } from '../../../../../Dom/Slot';
import { Emit } from '../../../../../events/Decorators/EmitDecorator';
import { KeyboardController } from '../../../../Controllers/KeyboardController';
import { Attribute } from '../../../../Decorators/AttributeDecorator';
import { Property } from '../../../../Decorators/PropertyDecorator';
import { Key } from '../../../../Types/Key';
import { CustomElement } from '../../../Abstracts/CustomElement';
// #endregion
/**
 * Selector - An abstract foundation for single-item selection behavior with comprehensive keyboard navigation.
 *
 * @description
 * The SelectorElement provides the core functionality for components that support single-item
 * selection from a collection of child elements. This abstract base class manages selection state,
 * keyboard navigation (Home/End keys, next/previous selection), slot-based item discovery, and
 * reactive selection change notifications. It is designed to be extended by concrete implementations
 * such as dropdowns, listboxes, radio groups, tab panels, menu lists, or any component requiring
 * single-selection patterns. The element automatically tracks slotted child items, maintains
 * selected index and item references, and provides a comprehensive API for programmatic selection
 * control with full accessibility support.
 *
 * @name Selector
 * @category Selectors
 *
 * @slot - Default slot for selector item elements
 *
 * @fires selectionChanged {SelectionChangedEvent} - Fired when the selected item changes, providing both old and new selected items
 * @fires itemsChanged {ItemsChangedEvent} - Fired when the slotted items collection changes (items added, removed, or reordered)
 * @fires changed {PropertyChangedEvent} - Fired when any property changes
 * @fires connected {ConnectedEvent} - Fired when the element is connected to the DOM
 *
 * @keyhandle Home - Selects the first item in the collection
 * @keyhandle End - Selects the last item in the collection
 *
 * @example
 * Extending for a custom dropdown:
 * ```typescript
 * class DropdownElement extends SelectorElement<OptionElement> {
 *   protected override onSelectionChanged(oldIndex: number, newIndex: number): void {
 *     super.onSelectionChanged(oldIndex, newIndex);
 *     this.displayText = this.selectedItem?.displayText || '';
 *     this.close();
 *   }
 * }
 * ```
 *
 * @example
 * Programmatic selection control:
 * ```typescript
 * const selector = document.querySelector('my-selector');
 * selector.select(0);           // Select by index
 * selector.select('option-1');  // Select by value
 * selector.selectNext();        // Move to next item
 * selector.selectPrevious();    // Move to previous item
 * selector.selectFirst();       // Jump to first item
 * selector.selectLast();        // Jump to last item
 * selector.resetSelection();    // Clear selection
 * ```
 *
 * @example
 * Listening to selection changes:
 * ```typescript
 * const selector = document.querySelector('my-selector');
 * selector.addEventListener('selectionChanged', (event) => {
 *   const { oldValue, newValue } = event.detail;
 *   console.log('Selection changed from', oldValue, 'to', newValue);
 *   console.log('Selected index:', selector.selectedIndex);
 *   console.log('Selected item:', selector.selectedItem);
 * });
 * ```
 *
 * @example
 * Using with slotted items:
 * ```html
 * <my-selector>
 *   <my-item value="1">Option One</my-item>
 *   <my-item value="2">Option Two</my-item>
 *   <my-item value="3">Option Three</my-item>
 * </my-selector>
 * ```
 *
 * @public
 * @abstract
 */
export class SelectorElement extends CustomElement {
    // #region Fields
    _keyboardController;
    _itemsChanged;
    _selectionChanged;
    _selectedItem;
    _selectedIndex;
    _slotChangeSubscription;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._selectedItem = null;
        this._selectedIndex = -1;
        this._selectionChanged = new HTMLElementEventEmitter(this, 'selectionChanged');
        this._itemsChanged = new HTMLElementEventEmitter(this, 'itemsChanged');
        this._slotChangeSubscription = null;
        this._keyboardController = new KeyboardController(this);
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     * The `is` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-selector';
    }
    /**
     * Gets or sets the `items` property.
     *
     * @private
     * @readonly
     */
    get items() {
        return getSlotElements(this.shadowRoot);
    }
    /**
     * Gets a value that indicates whether the selector contains items.
     *
     * @private
     * @readonly
     */
    get hasItems() {
        return this.items.length > 0;
    }
    /**
     * Gets the first item in the current selection or returns null if the selection is empty.
     *
     * @private
     * @readonly
     */
    get selectedItem() {
        return this._selectedItem;
    }
    set selectedItem(value) {
        if (this._selectedItem !== value) {
            this._selectedItem = value;
            this.requestUpdate('selectedItem');
        }
    }
    /**
     * Gets the index of the first item in the current selection or returns -1 if the selection is empty.
     *
     * @private
     * @readonly
     */
    get selectedIndex() {
        return this._selectedIndex;
    }
    set selectedIndex(value) {
        if (this._selectedIndex !== value) {
            this._selectedIndex = value;
            this.requestUpdate('selectedIndex');
        }
    }
    /**
     * Called when the selection has changed.
     * Provides reference to `ISelectionChangedEventDetail` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get selectionChanged() {
        return this._selectionChanged;
    }
    /**
     * Called when the items has changed.
     * Provides reference to `IItemsChangedEventDetail` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get itemsChanged() {
        return this._itemsChanged;
    }
    /**
     * @protected
     * @readonly
     */
    get keyboard() {
        return this._keyboardController;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this.keyboard
            .add([Key.Home], () => this.selectFirst(), undefined, { preventDefault: true })
            .add([Key.End], () => this.selectLast(), undefined, { preventDefault: true });
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        this._slotChangeSubscription?.dispose();
    }
    /**
     * Select the item.
     *
     * @public
     * @param item - The element, index or value to select.
     */
    select(item) {
        let current = null;
        const items = this.items;
        if (typeof item === 'string') {
            current = items.find((x) => x.value === item) ?? null;
        }
        else if (typeof item === 'number') {
            current = items.find((_, i) => i === item) ?? null;
        }
        else {
            current = item;
        }
        if (current !== null) {
            this.onSelectionChanged(this._selectedItem ? this._selectedItem.index : -1, current.index);
        }
    }
    /**
     * Deselect the item.
     *
     * @public
     * @param item - The element or index to deselect.
     */
    deselect(item) {
        const current = typeof item === 'number'
            ? this.items.find((_, i) => i === item)
            : item;
        if (current) {
            this.onSelectionChanged(this._selectedItem ? this._selectedItem.index : -1, current.index);
        }
    }
    /**
     * Resets the current selection.
     *
     * @public
     */
    resetSelection() {
        if (this._selectedIndex > 0 && this._selectedItem) {
            const current = this._selectedItem;
            current.deselect();
            this.selectedItem = null;
            this.selectedIndex = -1;
            this.onSelectionChanged(current.index, -1);
        }
    }
    /**
     * Select the next item after current selected item/index.
     *
     * @public
     */
    selectNext() {
        const items = this.items;
        const index = this._selectedItem ? this._selectedItem.index : -1;
        const hasNext = index >= 0 && index + 1 < items.length;
        if (hasNext) {
            this.select(index + 1);
        }
    }
    /**
     * Select the previous item before current selected item/index.
     *
     * @public
     */
    selectPrevious() {
        const index = this._selectedItem ? this._selectedItem.index : -1;
        const hasPrevious = !(index - 1 < 0);
        if (hasPrevious) {
            this.select(index - 1);
        }
    }
    /**
     * Select the first item.
     *
     * @public
     */
    selectFirst() {
        this.select(0);
    }
    /**
     * Select the last item.
     *
     * @public
     */
    selectLast() {
        this.select(this.items.length - 1);
    }
    // /**
    //  * @protected
    //  * @override
    //  */
    // protected override async getUpdateComplete(): Promise<boolean> {
    //     await super.getUpdateComplete();
    //     const items = this.items;
    //     return items.length > 0;
    // }
    /**
     * @protected
     * @override
     */
    onApplyTemplate() {
        super.onApplyTemplate();
        const slot = getSlot(this.shadowRoot);
        if (slot) {
            this._slotChangeSubscription = on(slot, 'slotchange', () => this.onItemsChanged());
        }
    }
    /**
     * @protected
     * @virtual
     */
    onSelectionChanged(oldIndex, newIndex, cb) {
        if (oldIndex === newIndex) {
            return;
        }
        const items = this.items;
        // gt-or-eq null check is needed, otherwise the at method will return a value
        const oldItem = oldIndex >= 0
            ? items.at(oldIndex)
            : null;
        const newItem = newIndex >= 0
            ? items.at(newIndex)
            : null;
        if (newIndex >= 0 && newIndex < items.length) {
            if (oldItem) {
                oldItem.deselect();
            }
            if (newItem) {
                newItem.select();
                this.selectedItem = newItem;
                this.selectedIndex = newItem.index;
            }
            if (cb) {
                cb(oldItem, newItem);
            }
            this._selectionChanged.emit({
                oldValue: oldItem ?? null,
                newValue: newItem ?? null
            });
        }
    }
    /**
     * @protected
     * @virtual
     */
    onItemsChanged() {
        this._itemsChanged.emit();
    }
}
__decorate([
    Property({ type: Array }),
    __metadata("design:type", Array),
    __metadata("design:paramtypes", [])
], SelectorElement.prototype, "items", null);
__decorate([
    Property({ type: Object }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], SelectorElement.prototype, "selectedItem", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SelectorElement.prototype, "selectedIndex", null);
__decorate([
    Emit({ eventName: 'selectionChanged' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SelectorElement.prototype, "selectionChanged", null);
__decorate([
    Emit({ eventName: 'itemsChanged' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SelectorElement.prototype, "itemsChanged", null);
//# sourceMappingURL=SelectorElement.js.map