/**
 * 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.
 */
import { autoUpdate, computePosition, flip, offset, shift, size, platform } from '@floating-ui/dom';
import { PortalUtils } from '../nile-select/portal-utils';
export class PortalManager {
    constructor(component) {
        this.portalContainer = null;
        this.originalListboxParent = null;
        this.measuredPopupHeight = null;
        this.cleanupAutoUpdate = null;
        this.currentPlacement = 'bottom';
        this.currentMiddlewareData = null;
        this.component = component;
    }
    createPortalAppendContainer() {
        const container = document.createElement('div');
        container.style.position = 'absolute';
        container.style.zIndex = '9999';
        container.style.pointerEvents = 'none';
        container.className = 'nile-virtual-select-portal-append';
        return container;
    }
    positionPortalAppend() {
        if (!this.portalContainer || !this.component.popup)
            return;
        this.measurePopupHeight();
        this.computeFloatingUIPosition();
    }
    measurePopupHeight() {
        if (this.measuredPopupHeight || !this.portalContainer)
            return;
        // Temporarily position the container to measure its height
        this.portalContainer.style.position = 'absolute';
        this.portalContainer.style.visibility = 'hidden';
        this.portalContainer.style.top = '0px';
        this.portalContainer.style.left = '0px';
        // Force a reflow to ensure the container is rendered
        this.portalContainer.offsetHeight;
        // Measure the actual height
        this.measuredPopupHeight = this.portalContainer.offsetHeight;
        // Reset visibility
        this.portalContainer.style.visibility = '';
    }
    async computeFloatingUIPosition() {
        if (!this.portalContainer)
            return;
        const referenceElement = this.component.combobox || this.component;
        const floatingElement = this.portalContainer;
        try {
            const { x, y, placement, middlewareData } = await this.calculateFloatingUIPosition(referenceElement, floatingElement);
            this.applyFloatingUIPosition(floatingElement, referenceElement, x, y, placement, middlewareData);
        }
        catch (error) {
            console.warn('Floating UI positioning failed, falling back to simple positioning:', error);
            this.fallbackPositioning();
        }
    }
    async calculateFloatingUIPosition(referenceElement, floatingElement) {
        const boundary = PortalUtils.findBoundaryElements(referenceElement);
        const initialPlacement = PortalUtils.getOptimalPlacement(referenceElement);
        const middleware = this.createFloatingUIMiddleware(boundary);
        return await computePosition(referenceElement, floatingElement, {
            placement: initialPlacement,
            strategy: 'fixed',
            middleware,
            platform: this.createCustomPlatform()
        });
    }
    createFloatingUIMiddleware(boundary) {
        return [
            offset(4),
            size({
                apply: this.handleSizeMiddleware.bind(this),
                padding: 10,
                boundary: boundary
            }),
            flip({
                fallbackPlacements: ['bottom', 'top', 'bottom-start', 'top-start', 'bottom-end', 'top-end'],
                fallbackStrategy: 'bestFit',
                padding: 10,
                boundary: boundary
            }),
            shift({
                padding: 10,
                crossAxis: true,
                boundary: boundary
            })
        ];
    }
    handleSizeMiddleware({ availableWidth, availableHeight, elements, rects }) {
        const maxHeight = PortalUtils.calculateOptimalHeight(rects.reference, window.innerHeight, this.currentPlacement);
        elements.floating.style.maxWidth = `${availableWidth}px`;
        elements.floating.style.maxHeight = `${maxHeight}px`;
        elements.floating.style.setProperty('--auto-size-available-width', `${availableWidth}px`);
        elements.floating.style.setProperty('--auto-size-available-height', `${maxHeight}px`);
    }
    createCustomPlatform() {
        return platform;
    }
    applyFloatingUIPosition(floatingElement, referenceElement, x, y, placement, middlewareData) {
        Object.assign(floatingElement.style, {
            left: `${x}px`,
            top: `${y}px`,
            position: 'fixed',
            pointerEvents: 'auto'
        });
        this.currentPlacement = placement;
        this.currentMiddlewareData = middlewareData;
        PortalUtils.applyCollisionData(floatingElement, middlewareData, placement);
        const placementClass = placement.split('-')[0];
        floatingElement.className = `nile-virtual-select-portal-append select__listbox--${placementClass}`;
        const referenceRect = referenceElement.getBoundingClientRect();
        floatingElement.style.width = `${referenceRect.width}px`;
    }
    fallbackPositioning() {
        if (!this.portalContainer)
            return;
        const referenceElement = this.component.combobox || this.component;
        const rect = referenceElement.getBoundingClientRect();
        const viewportHeight = window.innerHeight;
        const popupHeight = this.measuredPopupHeight || 200;
        const spaceBelow = viewportHeight - rect.bottom;
        const spaceAbove = rect.top;
        let topPosition;
        let placementClass;
        let maxHeight;
        if (spaceAbove > spaceBelow) {
            maxHeight = Math.max(spaceAbove - 20, 100);
            topPosition = Math.max(rect.top - maxHeight - 4, 10);
            placementClass = 'top';
        }
        else {
            maxHeight = Math.max(spaceBelow - 20, 100);
            topPosition = rect.bottom + 4;
            placementClass = 'bottom';
        }
        this.portalContainer.style.left = `${rect.left}px`;
        this.portalContainer.style.top = `${topPosition}px`;
        this.portalContainer.style.width = `${rect.width}px`;
        this.portalContainer.style.maxHeight = `${maxHeight}px`;
        this.portalContainer.style.pointerEvents = 'auto';
        this.portalContainer.className = `nile-virtual-select-portal-append select__listbox--${placementClass}`;
        this.calculateAndSetAutoSizeProperties(rect, topPosition, placementClass);
    }
    calculateAndSetAutoSizeProperties(rect, topPosition, placementClass) {
        if (!this.portalContainer)
            return;
        const viewportHeight = window.innerHeight;
        const viewportWidth = window.innerWidth;
        let availableHeight;
        if (placementClass === 'top') {
            availableHeight = rect.top - 10;
        }
        else {
            availableHeight = viewportHeight - rect.bottom - 10;
        }
        const availableWidth = Math.min(rect.width, viewportWidth - rect.left - 10);
        this.portalContainer.style.setProperty('--auto-size-available-height', `${Math.max(availableHeight, 100)}px`);
        this.portalContainer.style.setProperty('--auto-size-available-width', `${Math.max(availableWidth, 200)}px`);
    }
    updatePortalAppendPosition() {
        if (this.component.portal && this.portalContainer) {
            this.positionPortalAppend();
        }
    }
    handleWindowResize() {
        if (this.component.portal && this.portalContainer) {
            this.positionPortalAppend();
        }
    }
    setupAutoUpdatePositioning() {
        if (!this.portalContainer || !this.component)
            return;
        this.cleanupAutoUpdatePositioning();
        this.cleanupAutoUpdate = autoUpdate(this.component, this.portalContainer, () => {
            this.computeFloatingUIPosition();
        }, {
            ancestorScroll: true,
            ancestorResize: true,
            elementResize: true,
            layoutShift: true,
            animationFrame: true
        });
    }
    cleanupAutoUpdatePositioning() {
        if (this.cleanupAutoUpdate) {
            this.cleanupAutoUpdate();
            this.cleanupAutoUpdate = null;
        }
    }
    extractStylesAsCSS(styles) {
        if (typeof styles === 'string') {
            return styles;
        }
        if (Array.isArray(styles)) {
            return styles.map(style => this.extractStylesAsCSS(style)).join('\n');
        }
        if (styles && typeof styles === 'object' && styles.cssText) {
            return styles.cssText;
        }
        return '';
    }
    injectStylesToDocument() {
        if (!this.portalContainer)
            return;
        // Create a unique identifier for this component instance
        const styleId = `nile-virtual-select-styles-${Math.random().toString(36).substring(2, 11)}`;
        // Check if styles are already injected
        if (document.getElementById(styleId))
            return;
        // Get the component's styles
        const componentStyles = this.component.constructor.styles;
        if (!componentStyles)
            return;
        // Create a style element
        const styleElement = document.createElement('style');
        styleElement.id = styleId;
        styleElement.textContent = this.extractStylesAsCSS(componentStyles);
        // Inject into document head
        document.head.appendChild(styleElement);
        // Store reference for cleanup
        this.portalContainer.__injectedStyleId = styleId;
    }
    adoptStylesToPortalAppend() {
        if (!this.portalContainer)
            return;
        this.injectStylesToDocument();
    }
    setupPortalAppend() {
        if (!this.component.portal)
            return;
        this.component.updateComplete.then(() => {
            const listbox = this.component.shadowRoot?.querySelector('#listbox');
            if (listbox) {
                this.originalListboxParent = listbox.parentElement;
                this.portalContainer = this.createPortalAppendContainer();
                this.portalContainer.appendChild(listbox);
                document.body.appendChild(this.portalContainer);
                // Adopt the component's styles to the body append container
                this.adoptStylesToPortalAppend();
                listbox.style.display = '';
                this.positionPortalAppend();
                this.setupAutoUpdatePositioning();
                window.addEventListener('resize', this.handleWindowResize.bind(this));
            }
        });
    }
    cleanupPortalAppend() {
        this.cleanupAutoUpdatePositioning();
        if (this.portalContainer && this.portalContainer.parentNode) {
            const listbox = this.portalContainer.querySelector('#listbox');
            if (listbox && this.originalListboxParent) {
                this.originalListboxParent.appendChild(listbox);
                listbox.style.display = this.component.portal ? 'none' : '';
            }
            // Clean up injected styles if they exist
            const injectedStyleId = this.portalContainer.__injectedStyleId;
            if (injectedStyleId) {
                const styleElement = document.getElementById(injectedStyleId);
                if (styleElement) {
                    styleElement.remove();
                }
            }
            this.portalContainer.parentNode.removeChild(this.portalContainer);
        }
        window.removeEventListener('resize', this.handleWindowResize.bind(this));
        this.portalContainer = null;
        this.originalListboxParent = null;
        this.measuredPopupHeight = null;
        this.currentPlacement = 'bottom';
        this.currentMiddlewareData = null;
    }
    async resetScrollPosition() {
        await this.component.updateComplete;
        requestAnimationFrame(() => {
            let listbox = null;
            if (this.component.portal && this.portalContainer) {
                listbox = this.portalContainer.querySelector('#listbox');
            }
            else {
                listbox = this.component.shadowRoot?.querySelector('#listbox');
            }
            if (!listbox || !listbox.isConnected)
                return;
            listbox.scrollTop = 0;
            const virtualized = listbox.querySelector('.virtualized');
            if (virtualized && virtualized.isConnected) {
                const fewItems = this.component.data?.length < 5;
                if (fewItems) {
                    virtualized.style.overflowY = 'hidden';
                    virtualized.style.maxHeight = 'none';
                    listbox.style.overflowY = 'hidden';
                    listbox.style.maxHeight = 'fit-content';
                }
                else {
                    virtualized.style.overflowY = 'auto';
                    virtualized.style.maxHeight = '';
                    listbox.style.overflowY = 'auto';
                    listbox.style.maxHeight = '';
                }
                virtualized.scrollTop = 0;
            }
            const virtualizer = listbox.querySelector('lit-virtualizer');
            if (virtualizer && virtualizer.isConnected) {
                virtualizer.scrollTop = 0;
            }
        });
    }
    get portalContainerElement() {
        return this.portalContainer;
    }
    resetMeasuredHeight() {
        this.measuredPopupHeight = null;
    }
    forceReposition() {
        if (this.portalContainer) {
            this.computeFloatingUIPosition();
        }
    }
    getCurrentPlacement() {
        return this.currentPlacement;
    }
    getCurrentMiddlewareData() {
        return this.currentMiddlewareData;
    }
    isUsingFloatingUI() {
        return this.cleanupAutoUpdate !== null;
    }
    handleViewportChange() {
        if (this.portalContainer) {
            this.resetMeasuredHeight();
            this.forceReposition();
        }
    }
    isPositioningOptimal() {
        if (!this.portalContainer || !this.currentMiddlewareData)
            return true;
        const referenceElement = this.component.combobox || this.component;
        const rect = referenceElement.getBoundingClientRect();
        const viewportHeight = window.innerHeight;
        const spaceBelow = viewportHeight - rect.bottom;
        const spaceAbove = rect.top;
        const isAbove = this.currentPlacement.startsWith('top');
        const isBelow = this.currentPlacement.startsWith('bottom');
        if (isAbove && spaceBelow > spaceAbove)
            return false;
        if (isBelow && spaceAbove > spaceBelow)
            return false;
        return true;
    }
}
//# sourceMappingURL=portal-manager.js.map