import { MXBaseComponent, convertToPlacement, AXFocusableComponent, AXComponent } from '@acorex/cdk/common';
import { AXOverlayService } from '@acorex/cdk/overlay';
import { AXPlatform } from '@acorex/core/platform';
import { AXTranslationService } from '@acorex/core/translation';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, DOCUMENT, PLATFORM_ID, input, afterNextRender, EventEmitter, ElementRef, TemplateRef, Output, Input, ViewChild, ChangeDetectionStrategy, ViewEncapsulation, Component, NgModule } from '@angular/core';
import { Subject, fromEvent, delay } from 'rxjs';

/**
 * @category Components
 * A popover component with custom encapsulation and change detection settings.
 */
class AXPopoverComponent extends MXBaseComponent {
    constructor() {
        super(...arguments);
        this.platform = inject(AXPlatform);
        this.document = inject(DOCUMENT);
        this.platformID = inject(PLATFORM_ID);
        this.overlayService = inject(AXOverlayService);
        this.onKeyDownHandler = null;
        /** @ignore */
        this._overlayRef = null;
        /** @ignore */
        this._lastActiveElement = null;
        /** @ignore */
        this._targetEvents = {};
        /** @ignore */
        this._outsideClickHandler = null;
        /** @ignore */
        this._scrollHandler = null;
        /** @ignore */
        this._scrollListenerActive = false;
        /** @ignore */
        this._scrollActivationFrame = null;
        /** @ignore */
        this.resizeObserver = null;
        /** @ignore */
        this.resize$ = new Subject();
        /** @ignore */
        this._disabled = false;
        this.width = input('fit-content', ...(ngDevMode ? [{ debugName: "width" }] : []));
        this.disablePanelClass = input(false, ...(ngDevMode ? [{ debugName: "disablePanelClass" }] : []));
        this.translateService = inject(AXTranslationService);
        this.#init = afterNextRender(() => {
            const lang = this.translateService.getActiveLang();
            let defaultPlacementDirection;
            if (lang === 'en-US') {
                defaultPlacementDirection = 'bottom-start';
            }
            else {
                defaultPlacementDirection = 'bottom-end';
            }
            if (!this.placement)
                this.placement = defaultPlacementDirection;
            const converted = this.placement
                ? convertToPlacement(this.placement)
                : convertToPlacement(defaultPlacementDirection);
            this._placements = converted;
            this.resize$.subscribe(() => {
                if (this.isOpen) {
                    this.updatePosition();
                }
            });
        });
        /** @ignore */
        this._offsetX = 0;
        /** @ignore */
        this._offsetY = 0;
        /** @ignore */
        this._placements = [];
        /** @ignore */
        this._placement = null;
        /** @ignore */
        this._componentRef = null;
        /** @ignore */
        this._openOn = 'toggle';
        /** @ignore */
        this._closeOn = 'clickOut';
        /**
         * Determines if a backdrop should be displayed behind the popover.
         */
        this.hasBackdrop = false;
        /**
         * Delay in milliseconds before the popover opens after a trigger event.
         */
        this._openAfter = 200;
        /**
         * Delay in milliseconds before the popover closes after a trigger event.
         */
        this._closeAfter = 200;
        /**
         * Whether to close the popover when scrolling occurs outside of it.
         * Scrolling within the popover (or any nested overlay) will not close it.
         */
        this.closeOnScroll = true;
        /**
         * Enables or disables adaptivity.
         */
        this.adaptivityEnabled = false;
        /**
         * Emits when the component is opened.
         * @event
         */
        this.onOpened = new EventEmitter();
        /**
         * Emits when the component is closed.
         * @event
         */
        this.onClosed = new EventEmitter();
    }
    #init;
    /**
     * Gets or sets whether the popover is disabled.
     * When disabled, the popover will not open and will close if already open.
     */
    get disabled() {
        return this._disabled;
    }
    set disabled(v) {
        this._disabled = v;
        if (this._disabled && this.isOpen) {
            this.close();
        }
    }
    /**
     * Gets or sets the horizontal offset and updates it accordingly.
     */
    get offsetX() {
        return this._offsetX;
    }
    /**
     * Sets the horizontal offset and updates it with a callback.
     */
    set offsetX(v) {
        this.setOption({
            name: 'offsetX',
            value: v,
            afterCallback: () => {
                this.updateOffset();
            },
        });
    }
    /**
     * Vertical offset with update callback.
     */
    get offsetY() {
        return this._offsetY;
    }
    /**
     * Sets the vertical offset and updates the component position.
     */
    set offsetY(v) {
        this.setOption({
            name: 'offsetY',
            value: v,
            afterCallback: () => {
                this.updateOffset();
            },
        });
    }
    /**
     * Gets or sets the target element, supporting Element, ElementRef, or MXBaseComponent.
     */
    get target() {
        return this._target;
    }
    /**
     * Sets the target element and updates event bindings.
     */
    set target(v) {
        this.removeTargetEvents();
        if (v instanceof Element)
            this._target = v;
        else if (v instanceof ElementRef)
            this._target = v.nativeElement;
        else if (typeof v?.getHostElement == 'function')
            this._target = v.getHostElement();
        this.bindTargetEvents();
    }
    /**
     * Gets the current placement of the component.
     */
    get placement() {
        return this._placement;
    }
    /**
     * Sets the component's placement and updates its position settings.
     */
    set placement(v) {
        this.setOption({
            name: 'placement',
            value: v,
            afterCallback: (o, n) => {
                if (!n)
                    return;
                const converted = convertToPlacement(n);
                this._placements = converted;
                this.updateOffset();
                if (this._overlayRef) {
                    this._overlayRef.updatePosition();
                }
            },
        });
    }
    /**
     * Gets the reference to the component.
     */
    get componentRef() {
        return this._componentRef;
    }
    /**
     * Gets or sets the trigger for opening the popover and updates target events.
     */
    get openOn() {
        return this._openOn;
    }
    /**
     * Sets the trigger for opening the popover and updates target events.
     */
    set openOn(v) {
        this.setOption({
            name: 'openOn',
            value: v,
            afterCallback: () => {
                this.bindTargetEvents();
            },
        });
    }
    /**
     * Gets or sets the trigger for closing the popover.
     */
    get closeOn() {
        return this._closeOn;
    }
    /**
     * Sets the trigger for closing the popover and rebinds target events.
     */
    set closeOn(v) {
        this.setOption({
            name: 'closeOn',
            value: v,
            afterCallback: () => {
                this.bindTargetEvents();
            },
        });
    }
    get openAfter() {
        return this._openAfter;
    }
    set openAfter(value) {
        this._openAfter = value;
        this.bindTargetEvents();
    }
    get closeAfter() {
        return this._closeAfter;
    }
    set closeAfter(value) {
        this._closeAfter = value;
        this.bindTargetEvents();
    }
    /** @ignore */
    _emitOnOpenedEvent() {
        this.onOpened.emit({
            component: this,
            htmlElement: this.getHostElement(),
        });
    }
    /** @ignore */
    _emitOnClosedEvent() {
        this.onClosed.emit({
            component: this,
            htmlElement: this.getHostElement(),
        });
    }
    /** @ignore */
    removeTargetEvents() {
        Object.entries(this._targetEvents).forEach((e) => {
            e[1]?.unsubscribe();
        });
        this._targetEvents = {};
    }
    /** @ignore */
    bindTargetEvents() {
        this.removeTargetEvents();
        if (!this._target)
            return;
        const targetMouseEnter$ = fromEvent(this._target, 'mouseenter');
        const targetMouseLeave$ = fromEvent(this._target, 'mouseleave');
        if (this.openOn === 'hover') {
            this._targetEvents.mouseenter = targetMouseEnter$.pipe(delay(this.openAfter)).subscribe(() => {
                this.open();
            });
        }
        if (this.closeOn === 'leave') {
            this._targetEvents.mouseleave = targetMouseLeave$.pipe(delay(this.closeAfter)).subscribe(() => {
                this.close();
            });
        }
        if (this.openOn === 'click' || this.openOn === 'toggle') {
            const click$ = fromEvent(this._target, 'click');
            this._targetEvents.click = click$.subscribe(() => {
                this.openOn === 'toggle' ? this.toggle() : this.open();
            });
        }
    }
    /** @ignore */
    bindOverlayEvents() {
        const overlayElement = this._overlayRef?.overlayElement;
        if (!overlayElement)
            return;
        overlayElement.style.pointerEvents = 'none';
        // Outside click handler
        if (this.closeOn === 'clickOut') {
            this._outsideClickHandler = (e) => {
                const el = e.target;
                if (this.isOpen && !this._target?.contains(el) && !overlayElement?.contains(el)) {
                    // Check if the click is inside a child overlay (e.g., selectbox dropdown)
                    // Child overlays have a higher z-index than this popover
                    if (this.isInsideChildOverlay(el)) {
                        return;
                    }
                    this.close();
                }
            };
            this.document.addEventListener('mousedown', this._outsideClickHandler);
        }
        // Scroll listener to close popover when scrolling outside
        this.setupScrollListener(overlayElement);
        // Resize observer
        this.initializeResizeObserver();
        setTimeout(() => {
            overlayElement.style.pointerEvents = 'auto';
        }, this.openAfter);
    }
    /** @ignore */
    removeOverlayEvents() {
        if (this._outsideClickHandler) {
            this.document.removeEventListener('mousedown', this._outsideClickHandler);
            this._outsideClickHandler = null;
        }
        this.removeScrollListener();
    }
    /** @ignore */
    setupScrollListener(overlayElement) {
        if (!this.closeOnScroll || !isPlatformBrowser(this.platformID)) {
            return;
        }
        this._scrollHandler = (e) => {
            // Ignore scroll events fired during the initial open frame
            if (!this._scrollListenerActive || !this.isOpen) {
                return;
            }
            // Resolve the scroll target. For document/window scrolls, e.target is the document.
            const target = e.target;
            // Don't close when scrolling occurs inside the popover itself
            if (target && overlayElement.contains(target)) {
                return;
            }
            // Don't close when scrolling occurs inside a nested/child overlay
            // (e.g., a selectbox dropdown opened from within this popover).
            if (target instanceof Element && this.isInsideChildOverlay(target)) {
                return;
            }
            this.close();
        };
        // Capture phase so we receive scroll events from any scrollable ancestor
        this.document.addEventListener('scroll', this._scrollHandler, true);
        // Delay activation to ignore layout/scroll events fired right after opening
        this._scrollActivationFrame = requestAnimationFrame(() => {
            this._scrollListenerActive = true;
            this._scrollActivationFrame = null;
        });
    }
    /** @ignore */
    removeScrollListener() {
        if (this._scrollActivationFrame !== null) {
            cancelAnimationFrame(this._scrollActivationFrame);
            this._scrollActivationFrame = null;
        }
        if (this._scrollHandler) {
            this.document.removeEventListener('scroll', this._scrollHandler, true);
            this._scrollHandler = null;
        }
        this._scrollListenerActive = false;
    }
    /**
     * Checks whether the given element lives inside a child overlay
     * (an overlay stacked on top of this popover, e.g. a selectbox dropdown).
     * Child overlays are identified by having a higher z-index than this popover's overlay.
     * @ignore
     */
    isInsideChildOverlay(element) {
        const overlayElement = this._overlayRef?.overlayElement;
        if (!overlayElement)
            return false;
        const otherOverlay = element.closest('.ax-overlay-container');
        if (!otherOverlay || otherOverlay === overlayElement)
            return false;
        const popoverZIndex = parseInt(overlayElement.style.zIndex || '0', 10);
        const otherZIndex = parseInt(otherOverlay.style.zIndex || '0', 10);
        return otherZIndex > popoverZIndex;
    }
    /** @ignore */
    handleOverlayDisposed() {
        // Called when overlay is disposed externally (e.g., by scroll)
        this.disconnectResizeObserver();
        this.removeOverlayEvents();
        this.removeKeyDownListener();
        this._overlayRef = null;
        this._componentRef = null;
        this.restoreFocus();
        this._emitOnClosedEvent();
    }
    /**
     * Toggles the component's open state.
     */
    toggle() {
        this.isOpen ? this.close() : this.open();
    }
    /**
     * Closes the component if it's open, detaches the overlay, restores focus, and emits the closed event.
     */
    close() {
        if (!this.isOpen) {
            return;
        }
        this.disconnectResizeObserver();
        this.removeOverlayEvents();
        this.removeKeyDownListener();
        if (this._overlayRef) {
            this._overlayRef.dispose();
            this._overlayRef = null;
        }
        this._componentRef = null;
        this.restoreFocus();
        this._emitOnClosedEvent();
    }
    /**
     * Opens the component if it's not already open, saves focus, opens the overlay, and emits the opened event.
     */
    async open() {
        if (this.isOpen || this.disabled) {
            return;
        }
        this.addKeyDownListener();
        this.saveFocus();
        await this.openOverlayInternal();
        this._emitOnOpenedEvent();
    }
    /** @ignore */
    addKeyDownListener() {
        if (isPlatformBrowser(this.platformID)) {
            this.onKeyDownHandler = (e) => {
                if (e.key === 'Escape') {
                    this.close();
                }
            };
            this.document.addEventListener('keydown', this.onKeyDownHandler);
        }
    }
    /** @ignore */
    removeKeyDownListener() {
        if (this.onKeyDownHandler) {
            this.document.removeEventListener('keydown', this.onKeyDownHandler);
            this.onKeyDownHandler = null;
        }
    }
    /** @ignore */
    saveFocus() {
        if (isPlatformBrowser(this.platformID)) {
            this._lastActiveElement = this.document.activeElement;
        }
    }
    /** @ignore */
    restoreFocus() {
        if (this._lastActiveElement instanceof HTMLElement) {
            this._lastActiveElement?.focus?.();
        }
    }
    /** @ignore */
    async openOverlayInternal() {
        const targetRef = this._target;
        if (!targetRef)
            return;
        // Build panel classes
        const panelClasses = ['ax-animate-fadeIn', 'ax-animate-faster'];
        if (this.platform.isDark()) {
            panelClasses.push('ax-dark');
        }
        if (this.panelClass) {
            panelClasses.push(this.panelClass);
        }
        // Determine content to display
        let contentToRender;
        let inputs;
        if (this.content instanceof TemplateRef) {
            contentToRender = this.content;
        }
        else if (typeof this.content === 'function') {
            contentToRender = this.content;
            inputs = this.context;
        }
        else {
            contentToRender = this._baseTemplate;
        }
        // Create the overlay
        this._overlayRef = await this.overlayService.create(contentToRender, {
            inputs,
            anchorOptions: {
                anchor: targetRef,
                placement: this._placement,
                offsetX: this._offsetX,
                offsetY: this._offsetY,
                autoFlip: true,
            },
            panelClass: !this.disablePanelClass() ? panelClasses : '',
            width: this.width(),
            onDispose: () => this.handleOverlayDisposed(),
            actionSheetStyle: this.adaptivityEnabled,
        });
        // Store component ref if it's a component
        if (this._overlayRef.instance && 'instance' in this._overlayRef.instance) {
            this._componentRef = this._overlayRef.instance;
            if (this.context && this._componentRef.instance) {
                Object.assign(this._componentRef.instance, this.context);
            }
        }
        // Bind overlay events
        this.bindOverlayEvents();
    }
    /**
     * Checks if the overlay is currently open.
     * @returns {boolean}
     */
    get isOpen() {
        return this._overlayRef !== null;
    }
    /** @ignore */
    updateOffset() {
        this._placements?.forEach((p) => {
            if (this._offsetY != null)
                p.offsetY = this._offsetY;
            if (this._offsetX != null)
                p.offsetX = this._offsetX;
        });
    }
    /**
     * Updates the position of the overlay and focuses on it.
     */
    updatePosition() {
        this._overlayRef?.updatePosition();
        this.focus();
    }
    /**
     * Focuses the overlay element.
     */
    focus() {
        // Optional focus behavior
    }
    /**
     * Brings this popover to the front of all other overlays.
     */
    bringToFront() {
        this._overlayRef?.bringToFront();
    }
    /** @ignore */
    initializeResizeObserver() {
        if (!isPlatformBrowser(this.platformID) || !this._overlayRef?.overlayElement) {
            return;
        }
        this.disconnectResizeObserver();
        this.resizeObserver = new ResizeObserver(() => {
            this.resize$.next();
        });
        this.resizeObserver.observe(this._overlayRef.overlayElement);
    }
    /** @ignore */
    disconnectResizeObserver() {
        if (this.resizeObserver) {
            this.resizeObserver.disconnect();
            this.resizeObserver = null;
        }
    }
    /** @ignore */
    ngOnDestroy() {
        this.disconnectResizeObserver();
        this.resize$.complete();
        this.removeTargetEvents();
        this.removeOverlayEvents();
        this.removeKeyDownListener();
        if (this._overlayRef) {
            this._overlayRef.dispose();
            this._overlayRef = null;
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPopoverComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.3", type: AXPopoverComponent, isStandalone: true, selector: "ax-popover", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, disablePanelClass: { classPropertyName: "disablePanelClass", publicName: "disablePanelClass", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, offsetX: { classPropertyName: "offsetX", publicName: "offsetX", isSignal: false, isRequired: false, transformFunction: null }, offsetY: { classPropertyName: "offsetY", publicName: "offsetY", isSignal: false, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: false, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: false, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: false, isRequired: false, transformFunction: null }, openOn: { classPropertyName: "openOn", publicName: "openOn", isSignal: false, isRequired: false, transformFunction: null }, closeOn: { classPropertyName: "closeOn", publicName: "closeOn", isSignal: false, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: false, isRequired: false, transformFunction: null }, openAfter: { classPropertyName: "openAfter", publicName: "openAfter", isSignal: false, isRequired: false, transformFunction: null }, closeAfter: { classPropertyName: "closeAfter", publicName: "closeAfter", isSignal: false, isRequired: false, transformFunction: null }, closeOnScroll: { classPropertyName: "closeOnScroll", publicName: "closeOnScroll", isSignal: false, isRequired: false, transformFunction: null }, backdropClass: { classPropertyName: "backdropClass", publicName: "backdropClass", isSignal: false, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: false, isRequired: false, transformFunction: null }, adaptivityEnabled: { classPropertyName: "adaptivityEnabled", publicName: "adaptivityEnabled", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onOpened: "onOpened", onClosed: "onClosed" }, providers: [
            { provide: AXFocusableComponent, useExisting: AXPopoverComponent },
            { provide: AXComponent, useExisting: AXPopoverComponent },
        ], viewQueries: [{ propertyName: "_baseTemplate", first: true, predicate: ["baseTemplate"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<ng-template #baseTemplate>\n  <ng-content></ng-content>\n</ng-template>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPopoverComponent, decorators: [{
            type: Component,
            args: [{ selector: 'ax-popover', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
                        { provide: AXFocusableComponent, useExisting: AXPopoverComponent },
                        { provide: AXComponent, useExisting: AXPopoverComponent },
                    ], template: "<ng-template #baseTemplate>\n  <ng-content></ng-content>\n</ng-template>\n" }]
        }], propDecorators: { disabled: [{
                type: Input
            }], offsetX: [{
                type: Input
            }], offsetY: [{
                type: Input
            }], target: [{
                type: Input
            }], placement: [{
                type: Input
            }], _baseTemplate: [{
                type: ViewChild,
                args: ['baseTemplate', { static: true }]
            }], content: [{
                type: Input
            }], openOn: [{
                type: Input
            }], closeOn: [{
                type: Input
            }], hasBackdrop: [{
                type: Input
            }], openAfter: [{
                type: Input
            }], closeAfter: [{
                type: Input
            }], closeOnScroll: [{
                type: Input
            }], backdropClass: [{
                type: Input
            }], panelClass: [{
                type: Input
            }], adaptivityEnabled: [{
                type: Input
            }], onOpened: [{
                type: Output
            }], onClosed: [{
                type: Output
            }] } });

class AXPopoverModule {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPopoverModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.3", ngImport: i0, type: AXPopoverModule, imports: [CommonModule, AXPopoverComponent], exports: [AXPopoverComponent] }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPopoverModule, imports: [CommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPopoverModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: [CommonModule, AXPopoverComponent],
                    exports: [AXPopoverComponent],
                    providers: [],
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { AXPopoverComponent, AXPopoverModule };
//# sourceMappingURL=acorex-components-popover.mjs.map
