/**
 * Copyright Aquera Inc 2025
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */
var NileFloatingPanel_1;
import { __decorate } from "tslib";
import { customElement, property } from 'lit/decorators.js';
import { styles } from './nile-floating-panel.css';
import NileElement from '../internal/nile-element';
import tippy, { roundArrow, followCursor as followCursorPlugin, } from 'tippy.js';
import { parseFollowCursor, parseDuration, } from '../nile-lite-tooltip/utils';
import { VisibilityManager } from '../utilities/visibility-manager.js';
/**
 * Nile floating-panel component.
 *
 * A popover that supports rich content (title, body, actions).
 *
 * **Wrapper mode** (default): first child element is the trigger.
 * **For mode**: set `for="elementId"` to attach to an external element.
 *
 * @tag nile-floating-panel
 *
 * @fires nile-init   - Component initialized.
 * @fires nile-destroy - Component destroyed.
 * @fires nile-show   - Panel opened.
 * @fires nile-hide   - Panel closed.
 * @fires nile-after-show - Panel fully visible after animation.
 * @fires nile-after-hide - Panel fully hidden after animation.
 * @fires nile-toggle - Open/close transition (detail.open).
 * @fires nile-visibility-change - Hidden by scroll/tab change.
 */
let NileFloatingPanel = NileFloatingPanel_1 = class NileFloatingPanel extends NileElement {
    constructor() {
        super(...arguments);
        // ─── Tippy.js props ───
        this.placement = 'bottom';
        this.trigger = 'click';
        this.distance = 12;
        this.skidding = 0;
        this.arrow = 'round';
        this.animation = 'fade';
        this.duration = 200;
        this.delay = 0;
        this.interactive = true;
        this.interactiveBorder = 2;
        this.maxWidth = 'none';
        this.zIndex = 9999;
        this.followCursor = false;
        this.hideOnClick = true;
        this.inertia = false;
        this.allowHTML = false;
        this.flip = true;
        // ─── Popover-like props ───
        this.for = null;
        this.open = false;
        this.preventOverlayClose = false;
        this.title = '';
        this.disabled = false;
        /** When set, only one panel in the same group can be open at a time. */
        this.group = null;
        /** Close the panel when Escape is pressed. */
        this.closeOnEscape = true;
        /** Custom CSS class(es) added to the Tippy popper element, allowing per-instance styling from the host. */
        this.panelClass = '';
        // ─── Visibility manager props ───
        this.enableVisibilityEffect = false;
        this.enableTabClose = false;
        // ─── Internal state ───
        this.tippyInstance = null;
        this.panelContainer = null;
        this.anchorEl = null;
        this._suppressOpenWatch = false;
        this._panelId = `nile-fp-${Math.random().toString(36).slice(2, 9)}`;
        this._boundEscHandler = this._handleEscapeKey.bind(this);
        this._pendingShowListener = null;
        this._pendingHideListener = null;
    }
    static get prefersReducedMotion() {
        if (!NileFloatingPanel_1._reducedMotionQuery) {
            NileFloatingPanel_1._reducedMotionQuery =
                window.matchMedia('(prefers-reduced-motion: reduce)');
        }
        return NileFloatingPanel_1._reducedMotionQuery.matches;
    }
    static get styles() {
        return [styles];
    }
    createRenderRoot() {
        return this;
    }
    // ─── Lifecycle ───
    firstUpdated() {
        this._buildDOM();
        this._attachTippy();
        this._joinGroup();
        this.visibilityManager = new VisibilityManager({
            host: this,
            target: this.anchorEl || null,
            enableVisibilityEffect: this.enableVisibilityEffect,
            enableTabClose: this.enableTabClose,
            isOpen: () => this.open,
            onAnchorOutOfView: () => {
                this._setOpen(false);
                this.tippyInstance?.hide();
                this.emit('nile-visibility-change', {
                    visible: false,
                    reason: 'anchor-out-of-view',
                });
            },
            onDocumentHidden: () => {
                this._setOpen(false);
                this.tippyInstance?.hide();
                this.emit('nile-visibility-change', {
                    visible: false,
                    reason: 'document-hidden',
                });
            },
            emit: (event, detail) => this.emit(`nile-${event}`, detail),
        });
        this.emit('nile-init');
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this._cleanupPendingShowListener();
        this._cleanupPendingHideListener();
        this.visibilityManager?.cleanup();
        this._leaveGroup();
        this._removeEscListener();
        this._destroyTippy();
        this.emit('nile-destroy');
    }
    updated(changed) {
        super.updated(changed);
        if (!this.panelContainer)
            return;
        if (changed.has('open') && !this._suppressOpenWatch) {
            if (this.open) {
                this.visibilityManager?.setup();
                queueMicrotask(() => this.tippyInstance?.show());
            }
            else {
                this.visibilityManager?.cleanup();
                this.tippyInstance?.hide();
            }
        }
        if (changed.has('group')) {
            this._leaveGroup(changed.get('group'));
            this._joinGroup();
        }
        const rebuildProps = [
            'placement', 'trigger', 'distance', 'skidding', 'arrow',
            'animation', 'duration', 'delay', 'interactive', 'interactiveBorder',
            'maxWidth', 'zIndex', 'followCursor', 'hideOnClick', 'inertia',
            'allowHTML', 'flip', 'preventOverlayClose', 'disabled', 'width', 'height',
            'panelClass'
        ];
        if (rebuildProps.some(p => changed.has(p))) {
            this._attachTippy();
        }
    }
    // ─── Public API ───
    /** Programmatically shows the panel. Returns a promise that resolves after the show animation. */
    show() {
        this.open = true;
        return new Promise(resolve => {
            this._cleanupPendingShowListener();
            const handler = () => {
                this._pendingShowListener = null;
                resolve();
            };
            this._pendingShowListener = handler;
            this.addEventListener('nile-after-show', handler, { once: true });
        });
    }
    /** Programmatically hides the panel. Returns a promise that resolves after the hide animation. */
    hide() {
        this.open = false;
        return new Promise(resolve => {
            this._cleanupPendingHideListener();
            const handler = () => {
                this._pendingHideListener = null;
                resolve();
            };
            this._pendingHideListener = handler;
            this.addEventListener('nile-after-hide', handler, { once: true });
        });
    }
    _cleanupPendingShowListener() {
        if (this._pendingShowListener) {
            this.removeEventListener('nile-after-show', this._pendingShowListener);
            this._pendingShowListener = null;
        }
    }
    _cleanupPendingHideListener() {
        if (this._pendingHideListener) {
            this.removeEventListener('nile-after-hide', this._pendingHideListener);
            this._pendingHideListener = null;
        }
    }
    toggle() {
        this.open = !this.open;
    }
    refresh() {
        this._attachTippy();
    }
    /** Returns the current resolved placement from Tippy/Popper. */
    getCurrentPlacement() {
        const popper = this.tippyInstance?.popper;
        const box = popper?.querySelector('.tippy-box');
        return box?.dataset.placement ?? this.placement;
    }
    /** Returns true if the resolved placement matches the requested placement. */
    isPositioningOptimal() {
        return this.getCurrentPlacement() === this.placement;
    }
    // ─── Group management ───
    _joinGroup() {
        if (!this.group)
            return;
        let set = NileFloatingPanel_1._groups.get(this.group);
        if (!set) {
            set = new Set();
            NileFloatingPanel_1._groups.set(this.group, set);
        }
        set.add(this);
    }
    _leaveGroup(oldGroup) {
        const key = oldGroup ?? this.group;
        if (!key)
            return;
        const set = NileFloatingPanel_1._groups.get(key);
        if (set) {
            set.delete(this);
            if (set.size === 0)
                NileFloatingPanel_1._groups.delete(key);
        }
    }
    _hideGroupSiblings() {
        if (!this.group)
            return;
        const set = NileFloatingPanel_1._groups.get(this.group);
        if (!set)
            return;
        set.forEach(panel => {
            if (panel !== this && panel.open) {
                panel._setOpen(false);
                panel.tippyInstance?.hide();
            }
        });
    }
    // ─── Escape key ───
    _addEscListener() {
        if (this.closeOnEscape) {
            document.addEventListener('keydown', this._boundEscHandler);
        }
    }
    _removeEscListener() {
        document.removeEventListener('keydown', this._boundEscHandler);
    }
    _handleEscapeKey(e) {
        if (e.key === 'Escape' && this.open) {
            this._setOpen(false);
            this.tippyInstance?.hide();
        }
    }
    // ─── ARIA ───
    _applyAria() {
        if (!this.anchorEl || !this.panelContainer)
            return;
        this.panelContainer.setAttribute('role', 'dialog');
        this.panelContainer.id = this._panelId;
        this.anchorEl.setAttribute('aria-haspopup', 'dialog');
        this._syncAriaExpanded();
    }
    _syncAriaExpanded() {
        this.anchorEl?.setAttribute('aria-expanded', String(this.open));
        if (this.open) {
            this.anchorEl?.setAttribute('aria-describedby', this._panelId);
        }
        else {
            this.anchorEl?.removeAttribute('aria-describedby');
        }
    }
    // ─── DOM construction ───
    _buildDOM() {
        const children = Array.from(this.childNodes);
        this.anchorEl = null;
        const titleNodes = [];
        const actionNodes = [];
        const bodyNodes = [];
        let firstElementSeen = false;
        for (const child of children) {
            if (child instanceof HTMLElement) {
                const slot = child.getAttribute('slot');
                if (slot === 'title') {
                    child.removeAttribute('slot');
                    titleNodes.push(child);
                    continue;
                }
                if (slot === 'action') {
                    child.removeAttribute('slot');
                    actionNodes.push(child);
                    continue;
                }
                if (!firstElementSeen && !this.for) {
                    this.anchorEl = child;
                    firstElementSeen = true;
                    continue;
                }
            }
            bodyNodes.push(child);
        }
        if (this.for) {
            const anchor = document.getElementById(this.for);
            if (anchor) {
                this.anchorEl = anchor;
            }
        }
        while (this.firstChild) {
            this.removeChild(this.firstChild);
        }
        if (this.anchorEl && !this.for) {
            this.appendChild(this.anchorEl);
        }
        this.panelContainer = document.createElement('div');
        this.panelContainer.className = 'nile-floating-panel__content';
        this.panelContainer.style.display = 'none';
        const body = document.createElement('div');
        body.className = 'nile-floating-panel__body';
        if (titleNodes.length > 0 || this.title) {
            const titleDiv = document.createElement('div');
            titleDiv.className = 'nile-floating-panel__title';
            if (this.title) {
                titleDiv.textContent = this.title;
            }
            else {
                titleNodes.forEach(n => titleDiv.appendChild(n));
            }
            body.appendChild(titleDiv);
        }
        if (bodyNodes.length > 0) {
            const mainDiv = document.createElement('div');
            mainDiv.className = 'nile-floating-panel__main';
            bodyNodes.forEach(n => mainDiv.appendChild(n));
            body.appendChild(mainDiv);
        }
        if (actionNodes.length > 0) {
            const actionDiv = document.createElement('div');
            actionDiv.className = 'nile-floating-panel__action';
            actionNodes.forEach(n => actionDiv.appendChild(n));
            body.appendChild(actionDiv);
        }
        this.panelContainer.appendChild(body);
        this.appendChild(this.panelContainer);
        this._applyAria();
    }
    // ─── Tippy management ───
    _resolveArrow() {
        switch (this.arrow) {
            case 'round': return roundArrow;
            case 'none': return false;
            default: return true;
        }
    }
    _setOpen(value) {
        this._suppressOpenWatch = true;
        this.open = value;
        this._syncAriaExpanded();
        this._suppressOpenWatch = false;
    }
    _getEffectiveDuration() {
        if (NileFloatingPanel_1.prefersReducedMotion)
            return 0;
        return parseDuration(this.duration);
    }
    _getEffectiveAnimation() {
        if (NileFloatingPanel_1.prefersReducedMotion)
            return false;
        return this.animation;
    }
    _attachTippy() {
        this._destroyTippy();
        if (this.disabled || !this.anchorEl || !this.panelContainer)
            return;
        const resolvedFollowCursor = parseFollowCursor(this.followCursor);
        const effectiveHideOnClick = this.preventOverlayClose ? false : this.hideOnClick;
        const options = {
            content: this.panelContainer,
            placement: this.placement,
            trigger: this.trigger,
            offset: [this.skidding, this.distance],
            theme: 'floating-panel',
            animation: this._getEffectiveAnimation(),
            interactive: this.interactive,
            arrow: this._resolveArrow(),
            duration: this._getEffectiveDuration(),
            allowHTML: this.allowHTML,
            delay: this.delay,
            maxWidth: this.maxWidth,
            zIndex: this.zIndex,
            hideOnClick: effectiveHideOnClick,
            inertia: NileFloatingPanel_1.prefersReducedMotion ? false : this.inertia,
            interactiveBorder: this.interactiveBorder,
            appendTo: document.body,
            followCursor: resolvedFollowCursor,
            plugins: resolvedFollowCursor ? [followCursorPlugin] : [],
            popperOptions: {
                modifiers: [{ name: 'flip', enabled: this.flip }],
            },
            onMount: (instance) => {
                if (this.panelContainer)
                    this.panelContainer.style.display = '';
                if (this.panelClass) {
                    this.panelClass.split(/\s+/).filter(Boolean).forEach(cls => {
                        instance.popper.classList.add(cls);
                    });
                }
            },
            onShow: (instance) => {
                if (this.panelContainer)
                    this.panelContainer.style.display = '';
                const tc = instance.popper.querySelector('.tippy-content');
                if (tc) {
                    if (this.width)
                        tc.style.width = this.width;
                    if (this.height) {
                        tc.style.height = this.height;
                        tc.style.overflow = 'auto';
                    }
                }
                this._hideGroupSiblings();
                this._setOpen(true);
                this._addEscListener();
                this.dispatchEvent(new CustomEvent('nile-show', { detail: { instance, target: instance.reference } }));
                this.dispatchEvent(new CustomEvent('nile-toggle', { detail: { open: true, instance, target: instance.reference } }));
                return undefined;
            },
            onShown: (instance) => {
                this.dispatchEvent(new CustomEvent('nile-after-show', { detail: { instance, target: instance.reference } }));
            },
            onHide: (instance) => {
                this._setOpen(false);
                this._removeEscListener();
                this.dispatchEvent(new CustomEvent('nile-hide', { detail: { instance, target: instance.reference } }));
                this.dispatchEvent(new CustomEvent('nile-toggle', { detail: { open: false, instance, target: instance.reference } }));
                return undefined;
            },
            onHidden: (instance) => {
                if (this.panelContainer)
                    this.panelContainer.style.display = 'none';
                this.dispatchEvent(new CustomEvent('nile-after-hide', { detail: { instance, target: instance.reference } }));
            },
        };
        this.tippyInstance = tippy(this.anchorEl, options);
        if (this.open) {
            queueMicrotask(() => this.tippyInstance?.show());
        }
    }
    _destroyTippy() {
        if (this.tippyInstance) {
            this.tippyInstance.destroy();
            this.tippyInstance = null;
        }
        if (this.panelContainer) {
            this.panelContainer.style.display = 'none';
            if (this.panelContainer.parentElement !== this) {
                this.appendChild(this.panelContainer);
            }
        }
    }
};
NileFloatingPanel._groups = new Map();
NileFloatingPanel._reducedMotionQuery = null;
__decorate([
    property({ type: String })
], NileFloatingPanel.prototype, "placement", void 0);
__decorate([
    property({ type: String })
], NileFloatingPanel.prototype, "trigger", void 0);
__decorate([
    property({ type: Number })
], NileFloatingPanel.prototype, "distance", void 0);
__decorate([
    property({ type: Number })
], NileFloatingPanel.prototype, "skidding", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "arrow", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "animation", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "duration", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "delay", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "interactive", void 0);
__decorate([
    property({ type: Number, reflect: true })
], NileFloatingPanel.prototype, "interactiveBorder", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "maxWidth", void 0);
__decorate([
    property({ type: Number, reflect: true })
], NileFloatingPanel.prototype, "zIndex", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "followCursor", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "hideOnClick", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "inertia", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "allowHTML", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "flip", void 0);
__decorate([
    property({ type: String, attribute: 'for' })
], NileFloatingPanel.prototype, "for", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "open", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "preventOverlayClose", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "title", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "disabled", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "width", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "height", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileFloatingPanel.prototype, "group", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "closeOnEscape", void 0);
__decorate([
    property({ type: String, reflect: true, attribute: true })
], NileFloatingPanel.prototype, "panelClass", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "enableVisibilityEffect", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], NileFloatingPanel.prototype, "enableTabClose", void 0);
NileFloatingPanel = NileFloatingPanel_1 = __decorate([
    customElement('nile-floating-panel')
], NileFloatingPanel);
export { NileFloatingPanel };
export default NileFloatingPanel;
//# sourceMappingURL=nile-floating-panel.js.map