// #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 } from '@breadstone/mosaik-elements';
import { Emit } from '../../../../../events/Decorators/EmitDecorator';
import { Focusable } from '../../../../Behaviors/Focusable';
import { KeyboardController } from '../../../../Controllers/KeyboardController';
import { Attribute } from '../../../../Decorators/AttributeDecorator';
import { Key } from '../../../../Types/Key';
import { CustomElement } from '../../../Abstracts/CustomElement';
// #endregion
/**
 * Toggable - Abstract base class for elements with toggleable checked/unchecked states.
 *
 * @description
 * The ToggableElement provides comprehensive toggle behavior for interactive components that
 * support checked, unchecked, and optionally indeterminate states. This abstract base class
 * implements the complete state management lifecycle including keyboard interaction (Space/Enter
 * keys), state transition logic, and event emission for state changes. It supports both two-state
 * (checked/unchecked) and three-state (checked/unchecked/indeterminate) modes, making it ideal
 * as a foundation for checkboxes, radio buttons, toggle switches, expandable sections, and other
 * binary or tri-state UI controls. The class leverages the Focusable behavior for keyboard
 * accessibility and provides convenient methods (check, uncheck, toggle) for programmatic control.
 *
 * @name Toggable
 * @category Primitives
 *
 * @dependency {Focusable} - Focus management and keyboard interaction support
 * @dependency {KeyboardController} - Keyboard event handling and key binding management
 *
 * @fires checked {CheckedEvent} - Dispatched when the element transitions to checked state (isChecked = true)
 * @fires unchecked {UncheckedEvent} - Dispatched when the element transitions to unchecked state (isChecked = false)
 * @fires indeterminate {IndeterminateEvent} - Dispatched when the element transitions to indeterminate state (isChecked = null, requires isThreeState = true)
 *
 * @example
 * Creating a custom checkbox component:
 * ```typescript
 * @Component({
 *     selector: 'mosaik-checkbox',
 *     template: checkboxTemplate
 * })
 * export class CheckboxElement extends ToggableElement {
 *     public constructor() {
 *         super();
 *         this.isThreeState = false;
 *     }
 * }
 * ```
 *
 * @example
 * Using three-state toggle:
 * ```html
 * <custom-toggle is-three-state>
 *     <!-- Cycles through: unchecked → checked → indeterminate → unchecked -->
 * </custom-toggle>
 * ```
 *
 * @example
 * Programmatic control:
 * ```typescript
 * const toggle = document.querySelector('custom-toggle');
 * toggle.check();      // Set to checked
 * toggle.uncheck();    // Set to unchecked
 * toggle.toggle();     // Cycle to next state
 *
 * // Listen to state changes
 * toggle.checked.subscribe(() => {
 *     console.log('Element is now checked');
 * });
 * ```
 *
 * @example
 * Handling indeterminate state for parent-child relationships:
 * ```typescript
 * class SelectAllCheckbox extends ToggableElement {
 *     private updateFromChildren(children: Array<CheckboxElement>): void {
 *         const allChecked = children.every(c => c.isChecked);
 *         const someChecked = children.some(c => c.isChecked);
 *
 *         this.isThreeState = true;
 *         if (allChecked) {
 *             this.isChecked = true;
 *         } else if (someChecked) {
 *             this.isChecked = null;  // Indeterminate
 *         } else {
 *             this.isChecked = false;
 *         }
 *     }
 * }
 * ```
 *
 * @abstract
 * @public
 */
export class ToggableElement extends Focusable(CustomElement) {
    // #region Fields
    _checked;
    _unchecked;
    _indeterminate;
    _keyboardController;
    _isChecked;
    _isThreeState;
    // #endregion
    // #region Ctor
    constructor() {
        super();
        this._isChecked = false;
        this._isThreeState = false;
        this._checked = new HTMLElementEventEmitter(this, 'checked');
        this._unchecked = new HTMLElementEventEmitter(this, 'unchecked');
        this._indeterminate = new HTMLElementEventEmitter(this, 'indeterminate');
        this._keyboardController = new KeyboardController(this);
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `{@link is}` property.
     * The `{@link is}` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-toggable';
    }
    /**
     * Indicates whether the `{@link ToggableElement}` is checked.
     *
     * @public
     * @attr
     */
    get isChecked() {
        return this._isChecked;
    }
    set isChecked(value) {
        if (value === null && !this._isThreeState) {
            const exceptionMessage = 'The value \'null\' is not allowed, but the \'isThreeState\' property is currently \'false\'. For allows \'null values, the \'isThreeState\' property must be \'true\'.';
            throw new Error(exceptionMessage);
        }
        const isChecked = this._isThreeState
            ? value ?? null
            : value;
        if (this._isChecked !== isChecked) {
            this.onIsCheckedChanged(this._isChecked, value);
        }
    }
    /**
     * The `isThreeState` property determines whether the `{@link ToggableElement}` supports two or three states.
     * `isChecked` property can be set to `null` as a third state when `isThreeState` is `true`
     *
     * @public
     * @attr
     */
    get isThreeState() {
        return this._isThreeState;
    }
    set isThreeState(value) {
        if (this._isThreeState !== value) {
            this._isThreeState = value;
            this.requestUpdate('isThreeState');
        }
    }
    /**
     * Called when the `isChecked` property is `true`.
     * Provides reference to the `{@link ICheckedEventDetail}` as event argument.
     *
     * @public
     * @eventProperty
     * @readonly
     */
    get checked() {
        return this._checked;
    }
    /**
     * Called when the `isChecked` property is `false`.
     * Provides reference to the `{@link IUncheckedEventDetail}` as event argument.
     *
     * @public
     * @eventProperty
     * @readonly
     */
    get unchecked() {
        return this._unchecked;
    }
    /**
     * Called when the `isChecked` property is `null`.
     * Provides reference to the `{@link IIndeterminateEventDetail}` as event argument.
     *
     * @public
     * @eventProperty
     * @readonly
     */
    get indeterminate() {
        return this._indeterminate;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this._keyboardController
            .add([Key.Space, Key.Enter], () => this.toggle(), undefined, { preventDefault: true });
    }
    /**
     * @public
     */
    check() {
        if (this._isChecked) {
            return;
        }
        this.isChecked = true;
    }
    /**
     * @public
     */
    uncheck() {
        if (!this._isChecked) {
            return;
        }
        this.isChecked = false;
    }
    /**
     * @public
     */
    toggle() {
        this.isChecked = this.nextPossibleState(this._isChecked);
    }
    /**
     * This method is invoked when the `isChecked` property is changed.
     *
     * @protected
     */
    onIsCheckedChanged(_prev, newValue) {
        this._isChecked = newValue;
        this.requestUpdate('isChecked');
        if (newValue === true) {
            this._checked.emit();
        }
        else if (newValue === false) {
            this._unchecked.emit();
        }
        else {
            this._indeterminate.emit();
        }
    }
    /**
     * Returns the next possible state based on the current state.
     *
     * @private
     */
    nextPossibleState(current) {
        let next = null;
        if (this._isThreeState) {
            switch (current) {
                case true:
                    next = null;
                    break;
                case null:
                    next = false;
                    break;
                case false:
                    next = true;
                    break;
            }
        }
        else {
            next = !current;
        }
        return next;
    }
}
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], ToggableElement.prototype, "isChecked", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ToggableElement.prototype, "isThreeState", null);
__decorate([
    Emit({ eventName: 'checked' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], ToggableElement.prototype, "checked", null);
__decorate([
    Emit({ eventName: 'unchecked' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], ToggableElement.prototype, "unchecked", null);
__decorate([
    Emit({ eventName: 'indeterminate' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], ToggableElement.prototype, "indeterminate", null);
//# sourceMappingURL=ToggableElement.js.map