// #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 { RendererServiceLocator } from '../../../../Dom/Renderer';
import { Closeable, ICloseableProps } from '../../../Behaviors/Closeable';
import { Openable } from '../../../Behaviors/Openable';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { CustomElement } from '../../Abstracts/CustomElement';
/**
 * Default values for OverlayElement properties.
 *
 * @public
 */
export var IOverlayElementProps;
(function (IOverlayElementProps) {
    IOverlayElementProps.DEFAULTS = {
        isOpen: false,
        hasBackdrop: false,
        pressEscapeToClose: false,
        clickOutsideToClose: false,
        closeable: ICloseableProps.DEFAULTS.closeable,
    };
})(IOverlayElementProps || (IOverlayElementProps = {}));
/**
 * Overlay - Abstract base class for floating overlay UI elements with open/close lifecycle.
 *
 * @description
 * The OverlayElement provides foundational functionality for creating floating overlay components
 * such as modals, dialogs, popovers, dropdowns, tooltips, and side panels. This abstract base
 * class manages the complete overlay lifecycle including open/close state transitions, backdrop
 * rendering, keyboard interaction (Escape key to close), click-outside-to-close behavior, and
 * proper stacking context management for nested overlays. It combines the Openable and Closeable
 * behaviors to provide a consistent API for showing and hiding overlays, with built-in support
 * for async operations and cancellable close events. The class handles critical UX patterns like
 * ensuring only the topmost overlay responds to Escape key presses and proper cleanup of global
 * event listeners to prevent memory leaks.
 *
 * @name Overlay
 * @category Overlays
 *
 * @dependency {Openable} - Open action and lifecycle management
 * @dependency {Closeable} - Close action and lifecycle management
 *
 * @fires opened {OpenedEvent} - Dispatched when the overlay completes its open transition (isOpen becomes true)
 * @fires closed {ClosedEvent} - Dispatched when the overlay completes its close transition (isOpen becomes false)
 *
 * @example
 * Creating a custom modal dialog:
 * ```typescript
 * @Component({
 *     selector: 'mosaik-modal',
 *     template: modalTemplate
 * })
 * export class ModalElement extends OverlayElement {
 *     public constructor() {
 *         super();
 *         this.hasBackdrop = true;
 *         this.pressEscapeToClose = true;
 *         this.clickOutsideToClose = false;
 *     }
 * }
 * ```
 *
 * @example
 * Basic overlay usage:
 * ```html
 * <mosaik-custom-overlay
 *     has-backdrop
 *     press-escape-to-close
 *     click-outside-to-close>
 *     <h2>Overlay Content</h2>
 *     <p>This is an overlay panel.</p>
 * </mosaik-custom-overlay>
 * ```
 *
 * @example
 * Programmatic overlay control with async/await:
 * ```typescript
 * const overlay = document.querySelector('mosaik-custom-overlay');
 *
 * // Open the overlay
 * await overlay.open();
 * console.log('Overlay is now open');
 *
 * // Close the overlay (returns false if canceled)
 * const wasClosed = await overlay.close();
 * if (wasClosed) {
 *     console.log('Overlay was closed successfully');
 * }
 * ```
 *
 * @example
 * Handling nested overlays with proper Escape key behavior:
 * ```typescript
 * // When multiple overlays are open, only the topmost responds to Escape
 * const dialog1 = document.querySelector('#dialog-1');
 * const dialog2 = document.querySelector('#dialog-2');
 *
 * await dialog1.open();  // First overlay opens
 * await dialog2.open();  // Second overlay opens on top
 *
 * // Pressing Escape closes dialog2 (topmost), dialog1 remains open
 * // Pressing Escape again closes dialog1
 * ```
 *
 * @example
 * Cancellable close with event handling:
 * ```typescript
 * overlay.closed.subscribe((event) => {
 *     if (hasUnsavedChanges()) {
 *         event.cancel = true;  // Prevent closing
 *         showConfirmDialog();
 *     }
 * });
 * ```
 *
 * @abstract
 * @public
 */
export class OverlayElement extends Openable(Closeable(CustomElement)) {
    // #region Fields
    _isOpen;
    _hasBackdrop;
    _pressEscapeToClose;
    _clickOutsideToClose;
    _boundHandleKeyDown;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._isOpen = false;
        this._hasBackdrop = false;
        this._pressEscapeToClose = false;
        this._clickOutsideToClose = false;
        this._boundHandleKeyDown = null;
    }
    // #endregion
    // #region Properties
    /**
     * Gets or sets the `isOpen` property.
     *
     * @public
     * @attr
     */
    get isOpen() {
        return this._isOpen;
    }
    set isOpen(value) {
        if (this._isOpen !== value) {
            this.onIsOpenChanged(this._isOpen, value);
        }
    }
    /**
     * Gets or sets the `hasBackdrop` property.
     *
     * @public
     * @attr
     */
    get hasBackdrop() {
        return this._hasBackdrop;
    }
    set hasBackdrop(value) {
        if (this._hasBackdrop !== value) {
            this._hasBackdrop = value;
            this.requestUpdate('hasBackdrop');
        }
    }
    /**
     * Gets or sets the `pressEscapeToClose` property.
     *
     * @public
     * @attr
     */
    get pressEscapeToClose() {
        return this._pressEscapeToClose;
    }
    set pressEscapeToClose(value) {
        if (this._pressEscapeToClose !== value) {
            this._pressEscapeToClose = value;
            this.requestUpdate('pressEscapeToClose');
        }
    }
    /**
     * Gets or sets the `clickOutsideToClose` property.
     *
     * @public
     * @attr
     */
    get clickOutsideToClose() {
        return this._clickOutsideToClose;
    }
    set clickOutsideToClose(value) {
        if (this._clickOutsideToClose !== value) {
            this._clickOutsideToClose = value;
            this.requestUpdate('clickOutsideToClose');
        }
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this._boundHandleKeyDown = this.handleKeyDown.bind(this);
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        this.removeEscapeListener();
        super.disconnectedCallback();
    }
    /**
     * Opens the `OverlayElement`.
     *
     * @public
     * @override
     */
    async open() {
        if (this._isOpen) {
            return;
        }
        return super.open()
            .then(() => {
            this.isOpen = true;
            this.addEscapeListener();
        });
    }
    /**
     * Closes the `OverlayElement`.
     *
     * @public
     * @override
     */
    async close() {
        if (!this._isOpen) {
            return false;
        }
        this.removeEscapeListener();
        return super.close()
            .then((x) => {
            this.isOpen = false;
            return x;
        });
    }
    /**
     * This method is invoked when the `isOpen` property is changed.
     *
     * @protected
     */
    onIsOpenChanged(_prev, newValue) {
        this._isOpen = newValue;
        this.requestUpdate('isOpen');
        if (newValue) {
            void super.open();
            this.addEscapeListener();
        }
        else {
            this.removeEscapeListener();
            const args = { cancel: false };
            this.closed.emit(args);
        }
    }
    onHasBackdropChanged(_prev, next) {
        if (next) {
            RendererServiceLocator.current.setAttribute(this, 'aria-modal', 'true');
        }
        else {
            RendererServiceLocator.current.removeAttribute(this, 'aria-modal');
        }
    }
    /**
     * Handles the keydown event for the escape key.
     * Only closes if this overlay is the topmost one (last in DOM order among open overlays).
     *
     * @private
     */
    handleKeyDown(event) {
        if (!this._pressEscapeToClose || !this._isOpen || event.code !== 'Escape') {
            return;
        }
        // Find all open overlays that listen to escape
        const allOpenOverlays = Array.from(document.querySelectorAll('[is-open]'))
            .filter((el) => el instanceof OverlayElement && el.isOpen && el.pressEscapeToClose);
        // Only close if this is the topmost overlay (last in the list)
        if (allOpenOverlays.length > 0 && allOpenOverlays[allOpenOverlays.length - 1] === this) {
            event.stopPropagation();
            void this.close();
        }
    }
    /**
     * Adds the escape key listener to the document.
     *
     * @private
     */
    addEscapeListener() {
        if (this._boundHandleKeyDown && this._pressEscapeToClose) {
            document.addEventListener('keydown', this._boundHandleKeyDown);
        }
    }
    /**
     * Removes the escape key listener from the document.
     *
     * @private
     */
    removeEscapeListener() {
        if (this._boundHandleKeyDown) {
            document.removeEventListener('keydown', this._boundHandleKeyDown);
        }
    }
}
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], OverlayElement.prototype, "isOpen", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], OverlayElement.prototype, "hasBackdrop", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], OverlayElement.prototype, "pressEscapeToClose", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], OverlayElement.prototype, "clickOutsideToClose", null);
__decorate([
    Watch('hasBackdrop'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Boolean, Boolean]),
    __metadata("design:returntype", void 0)
], OverlayElement.prototype, "onHasBackdropChanged", null);
//# sourceMappingURL=OverlayElement.js.map