import { MXBaseComponent, convertToPlacement, AXFocusableComponent, AXComponent } from '@acorex/cdk/common';
import { AXPlatform } from '@acorex/core/platform';
import { AXHtmlUtil } from '@acorex/core/utils';
import { Overlay } from '@angular/cdk/overlay';
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, DOCUMENT, PLATFORM_ID, Renderer2, input, EventEmitter, ElementRef, TemplateRef, Output, Input, ViewChild, ChangeDetectionStrategy, ViewEncapsulation, Component, NgModule } from '@angular/core';
import { Subject, fromEvent, delay, debounceTime } 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.renderer = inject(Renderer2);
        this.onKeyDownListenerFn = () => undefined;
        /** @ignore */
        this._platform = inject(AXPlatform);
        /** @ignore */
        this._overlay = inject(Overlay);
        /** @ignore */
        this._isMouseOverButton = false;
        /** @ignore */
        this._isMouseOverTooltip = false;
        /** @ignore */
        this._targetEvents = {};
        /** @ignore */
        this._overlayEvents = {};
        /** @ignore */
        this.resize$ = new Subject();
        //
        /** @ignore */
        this._disabled = false;
        this.width = input('auto', ...(ngDevMode ? [{ debugName: "width" }] : []));
        /** @ignore */
        this._offsetX = 0;
        //
        /** @ignore */
        this._offsetY = 0;
        /** @ignore */
        this._defautPlacements = convertToPlacement('bottom-start');
        /** @ignore */
        this._placements = [...this._defautPlacements];
        /** @ignore */
        this._placement = this._placements;
        /** @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;
        /**
         * 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();
    }
    #platform;
    /**
     * 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) => {
                const converted = convertToPlacement(n);
                this._placements = converted.length ? converted : this._defautPlacements;
                this.updateOffset();
                this.updatePositionStrategy();
            },
        });
    }
    /**
     * 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.
     * @param v
     */
    set closeOn(v) {
        this.setOption({
            name: 'closeOn',
            value: v,
            afterCallback: () => {
                this.bindTargetEvents();
            },
        });
    }
    /** @ignore */
    _emitOnOpenedEvent() {
        this.onOpened.emit({
            component: this,
            htmlElement: this.getHostElement(),
        });
    }
    /** @ignore */
    _emitOnClosedEvent() {
        this.onClosed.emit({
            component: this,
            htmlElement: this.getHostElement(),
        });
    }
    //
    /** @ignore */
    _handleMouseEnter(e) {
        this._isMouseOverTooltip = true;
    }
    /** @ignore */
    _handleMouseLeave(e) {
        this._isMouseOverTooltip = false;
        setTimeout(() => {
            if (!this._isMouseOverButton && this.closeOn == 'leave') {
                this.close();
            }
        }, 250);
    }
    /** @ignore */
    removeTargetEvents() {
        Object.entries(this._targetEvents).forEach((e) => {
            e[1].unsubscribe();
        });
    }
    /** @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._isMouseOverButton = true;
                this.open();
            });
        }
        if (this.closeOn == 'leave') {
            this._targetEvents.mouseleave = targetMouseLeave$.pipe(delay(this.closeAfter)).subscribe(() => {
                this._isMouseOverButton = false;
                if (!this._isMouseOverTooltip) {
                    this.close();
                }
            });
        }
        if (this.openOn === 'click' || this.openOn === 'toggle') {
            const click$ = fromEvent(this._target, 'click');
            this._targetEvents.click = click$.subscribe((e) => {
                this.openOn == 'toggle' ? this.toggle() : this.open();
            });
        }
    }
    //
    /** @ignore */
    bindOverlayEvents() {
        this.removeOverlayEvents();
        this._overlayEvents.attach = this._overlayRef.attachments().subscribe(() => {
            this.initializeResizeObserver();
            if (this.openOn == 'hover') {
                this._overlayRef?.overlayElement?.addEventListener('mouseenter', this._handleMouseEnter.bind(this));
            }
            if (this.closeOn == 'leave') {
                this._overlayRef?.overlayElement?.addEventListener('mouseleave', this._handleMouseLeave.bind(this));
            }
            //
            this._overlayEvents.outside = this._overlayRef._outsidePointerEvents.subscribe((e) => {
                const el = e.target;
                if (this.closeOn == 'clickOut' &&
                    this.isOpen &&
                    !this._target.contains(el) &&
                    !this._overlayRef?.overlayElement?.contains(el)) {
                    this.close();
                }
            });
            //
            this._overlayEvents.scroll = this._platform.scroll.subscribe((c) => {
                //__axContext__
                const el = c.nativeEvent.target;
                if (!this.isActionsheetStyle && isPlatformBrowser(this.platformID)) {
                    if (el == this.document) {
                        this.close();
                        return;
                    }
                    if (el?.closest &&
                        !el.closest('.ax-overlay-pane') &&
                        !this._target.contains(el) &&
                        !this._overlayRef?.overlayElement?.contains(el)) {
                        this.close();
                    }
                }
            });
        });
    }
    /** @ignore */
    removeOverlayEvents() {
        this._overlayRef?.overlayElement?.removeEventListener('mouseenter', this._handleMouseEnter.bind(this));
        this._overlayRef?.overlayElement?.addEventListener('mouseleave', this._handleMouseLeave.bind(this));
        Object.entries(this._overlayEvents).forEach((e) => {
            e[1].unsubscribe();
        });
    }
    //
    /**
     * 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._overlayRef?.detach();
        this.restoreFocus();
        this._emitOnClosedEvent();
        this.onKeyDownListenerFn();
    }
    //
    /**
     * Opens the component if it's not already open, saves focus, opens the overlay, and emits the opened event.
     */
    open() {
        if (this.isOpen || this.disabled) {
            return;
        }
        if (isPlatformBrowser(this.platformID)) {
            this.onKeyDownListenerFn = this.renderer.listen(this.document.body, 'keydown', (e) => {
                if (e.key == 'Escape') {
                    this.close();
                }
            });
        }
        this.saveFocus();
        this.openOverlayInternal();
        this._emitOnOpenedEvent();
    }
    /** @ignore */
    saveFocus() {
        if (isPlatformBrowser(this.platformID)) {
            this._lastActiveElement = this.document.activeElement;
        }
    }
    /** @ignore */
    restoreFocus() {
        if (this._lastActiveElement instanceof HTMLElement) {
            if (this._lastActiveElement?.focus) {
                this._lastActiveElement.focus();
            }
        }
    }
    /** @ignore */
    openOverlayInternal() {
        const targetRef = this._target;
        if (!targetRef)
            return;
        //if (!this._overlayRef) {
        if (this.isActionsheetStyle) {
            this._overlayRef = this._overlay.create({
                positionStrategy: this._overlay.position().global().bottom().centerHorizontally(),
                disposeOnNavigation: true,
                scrollStrategy: this._overlay.scrollStrategies.block(),
                panelClass: [
                    'ax-actionsheet-base',
                    'ax-animate-slideInUp',
                    'ax-animate-faster',
                    this.panelClass ? this.panelClass : this.#platform.isDark() ? 'ax-dark' : '',
                ],
                hasBackdrop: true,
                width: '100%',
            });
        }
        else {
            this._overlayRef = this._overlay.create({
                positionStrategy: this._overlay.position().flexibleConnectedTo(targetRef).withPositions(this._placements),
                scrollStrategy: this._overlay.scrollStrategies.noop(),
                disposeOnNavigation: true,
                panelClass: ['ax-animate-fadeIn', 'ax-animate-faster', this.#platform.isDark() ? 'ax-dark' : ''],
                maxHeight: 'unset',
                hasBackdrop: this.hasBackdrop,
                backdropClass: [this.backdropClass || 'cdk-overlay-transparent-backdrop'],
                direction: AXHtmlUtil.isRtl(this.getHostElement()) ? 'rtl' : 'ltr',
                width: this.width(),
            });
            // this._overlayRef.setDirection()
        }
        //
        this.bindOverlayEvents();
        //}
        //
        if (this.content instanceof TemplateRef) {
            this._portal = new TemplatePortal(this.content, this.getViewContainer(), {
                $implicit: this.context,
                ref: this,
            });
            this._overlayRef?.attach(this._portal);
        }
        else if (typeof this.content === 'function') {
            this._portal = new ComponentPortal(this.content);
            this._componentRef = this._overlayRef?.attach(this._portal);
            Object.assign(this._componentRef.instance, this.context);
        }
        else {
            this._portal = new TemplatePortal(this._baseTemplate, this.getViewContainer(), {
                $implicit: this.context,
                ref: this,
            });
            this._overlayRef?.attach(this._portal);
        }
    }
    /**
     * Checks if the overlay is currently open.
     * @returns {boolean}
     */
    get isOpen() {
        return this._overlayRef ? this._overlayRef.hasAttached() : false;
    }
    /**
     * Determines if the actionsheet style should be applied.
     * @returns {boolean}
     */
    get isActionsheetStyle() {
        return this._platform.is('SM') && this.adaptivityEnabled;
    }
    /** @ignore */
    updatePositionStrategy() {
        const targetRef = this._target;
        if (!targetRef)
            return;
        let strategy;
        if (this.isActionsheetStyle) {
            strategy = this._overlay.position().global().bottom().centerHorizontally();
        }
        else {
            strategy = this._overlay.position().flexibleConnectedTo(targetRef).withPositions(this._placements);
        }
        this._overlayRef?.updatePositionStrategy(strategy);
    }
    /** @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() {
        //TODO: need this??
    }
    /** @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;
        }
    }
    //
    ngOnInit() {
        super.ngOnInit();
        // this.platformService.directionChange.subscribe((c) => {
        //   this.isRtl = c.data == 'rtl';
        // });
        this.resize$.pipe(debounceTime(50)).subscribe(() => {
            if (this.isOpen) {
                this.updatePosition();
            }
        });
    }
    /** @ignore */
    ngOnDestroy() {
        this.disconnectResizeObserver();
        this.resize$.complete();
        this.removeTargetEvents();
        this.removeOverlayEvents();
        this._overlayRef?.detach();
        this._overlayRef?.dispose();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXPopoverComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.9", type: AXPopoverComponent, isStandalone: true, selector: "ax-popover", inputs: { width: { classPropertyName: "width", publicName: "width", 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 }, 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.9", 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: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], 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
            }], 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.9", ngImport: i0, type: AXPopoverModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.9", ngImport: i0, type: AXPopoverModule, imports: [CommonModule, AXPopoverComponent], exports: [AXPopoverComponent] }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXPopoverModule, imports: [CommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", 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
