// #region Imports
import { on, waitForEvent } from '@breadstone/mosaik-elements';
import { RendererServiceLocator } from '../../../../Dom/Renderer';
import { EID } from '../../../../Utilities/EId';
import { PortalElement } from '../../Primitives/Portal/PortalElement';
import { SHEET_SERVICE_DEFAULT_CONFIG } from './ISheetServiceConfig';
import { SheetElement } from './SheetElement';
/**
 * Represents the `SHEET_DEFAULT_OPTIONS` constant.
 *
 * @private
 */
const SHEET_DEFAULT_OPTIONS = {
    height: 'auto'
};
/**
 * Service for managing the display and behavior of sheet elements.
 * Provides methods to open and close sheets within a portal structure.
 *
 * Uses a singleton pattern with static configuration.
 *
 * @example
 * ```typescript
 * // Configure once at app startup
 * SheetService.configure({
 *     closeOnNavigation: true,
 *     behaviors: []
 * });
 *
 * // Use throughout the app
 * const result = await SheetService.instance.open('mySheet', MySheetWrapper);
 * ```
 *
 * @public
 */
export class SheetService {
    // #region Fields
    static _instance = null;
    static _config = SHEET_SERVICE_DEFAULT_CONFIG;
    _sheetMap;
    _behaviors;
    _portalElement;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `SheetService` class.
     * Use `SheetService.configure()` and `SheetService.instance` instead of direct instantiation.
     *
     * @private
     */
    constructor(config) {
        this._sheetMap = new Map();
        this._behaviors = config.behaviors ?? [];
        if (config.closeOnNavigation !== false) {
            on(window, 'popstate', () => {
                if (this._sheetMap.size > 0) {
                    for (const [key, value] of this._sheetMap) {
                        if (value.options?.navigateToClose) {
                            void this.close(key);
                        }
                    }
                }
            });
        }
    }
    // #endregion
    // #region Properties
    /**
     * Gets the singleton instance of `SheetService`.
     * If not configured, uses default configuration.
     *
     * @public
     * @static
     */
    static get instance() {
        SheetService._instance ??= new SheetService(SheetService._config);
        return SheetService._instance;
    }
    // #endregion
    // #region Methods
    /**
     * Configures the `SheetService` globally.
     * Must be called before accessing `instance` if custom configuration is needed.
     * Can only be called once; subsequent calls will throw an error.
     *
     * @public
     * @static
     * @param config - The configuration for the sheet service.
     * @throws Error if already configured.
     *
     * @example
     * ```typescript
     * SheetService.configure({
     *     closeOnNavigation: true,
     *     behaviors: [myCustomBehavior]
     * });
     * ```
     */
    static configure(config) {
        SheetService._config = {
            ...SHEET_SERVICE_DEFAULT_CONFIG,
            ...config
        };
    }
    /**
     * Opens a custom wrapper component containing a sheet within a portal.
     *
     * @public
     * @template T - Type of the wrapper component extending HTMLElement.
     * @template TData - Type of data provided to the wrapper component.
     * @template TResult - Expected return type of the resolved sheet result.
     * @param sheetId - Unique identifier for the sheet instance.
     * @param wrapperClass - Class of the wrapper component containing the sheet.
     * @param options - Properties for portal and sheet.
     * @param data - Optional data passed to the wrapper component.
     * @returns A promise resolving with the sheet result.
     */
    async open(sheetId, wrapperClass, options, data) {
        // Allow behaviors to configure options
        const mergedOptions = { ...options };
        for (const behavior of this._behaviors) {
            behavior.configure(mergedOptions);
        }
        let ref = this._sheetMap.get(sheetId);
        if (!ref) {
            const wrapperElement = new wrapperClass(data);
            RendererServiceLocator.current.setProperty(wrapperElement, 'id', sheetId);
            const portalProjection = await this.extendToPortal(wrapperElement, mergedOptions);
            const sheetElement = wrapperElement.shadowRoot?.querySelector(SheetElement.is);
            if (!sheetElement) {
                throw new Error(`Sheet element not found within wrapper for ID "${sheetId}".`);
            }
            ref = {
                sheetId,
                sheetElement,
                portalProjectionElement: portalProjection,
                wrapperElement,
                options: mergedOptions
            };
            this._sheetMap.set(sheetId, ref);
        }
        Object.assign(ref.sheetElement, {
            ...SHEET_DEFAULT_OPTIONS,
            ...mergedOptions
        });
        // Notify behaviors about attachment
        for (const behavior of this._behaviors) {
            behavior.attach(ref);
        }
        return new Promise((resolve) => {
            ref.resolve = resolve;
            void ref.sheetElement.open();
            void waitForEvent(ref.sheetElement, 'closed').then(() => {
                // Notify behaviors about detachment
                for (const behavior of this._behaviors) {
                    behavior.detach(ref);
                }
                this._sheetMap.delete(sheetId);
                void ref.portalProjectionElement.close();
                ref.portalProjectionElement.remove();
            });
        });
    }
    /**
     * Closes the sheet in the wrapper component and optionally removes the wrapper from the DOM.
     *
     * @public
     * @template TResult - Type of result returned when the sheet is closed.
     * @param sheetId - Unique identifier for the sheet instance.
     * @param result - Optional result returned when closing the sheet.
     * @returns A promise resolving once the sheet is fully closed.
     */
    async close(sheetId, result) {
        return new Promise((resolve) => {
            const ref = this._sheetMap.get(sheetId);
            if (ref) {
                this._sheetMap.delete(sheetId);
                void waitForEvent(ref.sheetElement, 'closed').then(() => {
                    // Notify behaviors about detachment
                    for (const behavior of this._behaviors) {
                        behavior.detach(ref);
                    }
                    void ref.portalProjectionElement.close();
                    ref.portalProjectionElement.remove();
                    if (ref.resolve) {
                        ref.resolve(result);
                    }
                    resolve();
                });
                void ref.sheetElement.close();
            }
            else {
                console.error(`Wrapper with ID "${sheetId}" not found.`);
                resolve();
            }
        });
    }
    /**
     * Disposes of the service and cleans up resources.
     *
     * @public
     */
    dispose() {
        // Dispose all behaviors
        for (const behavior of this._behaviors) {
            behavior.dispose();
        }
        // Close all open sheets
        for (const [id] of this._sheetMap) {
            void this.close(id);
        }
        this._sheetMap.clear();
        // Clean up portal
        if (this._portalElement) {
            this._portalElement.remove();
            this._portalElement = undefined;
        }
    }
    /**
     * Extends the sheet component by placing it within a portal structure.
     *
     * @private
     * @param element - The element to place within the portal.
     * @param props - Properties to set on the portal.
     * @returns The created PortalProjectionElement containing the sheet.
     */
    extendToPortal(element, props) {
        return new Promise((resolve) => {
            if (!this._portalElement) {
                this._portalElement = RendererServiceLocator.current.createElement(PortalElement.is);
                RendererServiceLocator.current.setAttribute(this._portalElement, 'id', EID.next(PortalElement.is));
                Object.assign(this._portalElement, props);
                RendererServiceLocator.current.appendChild(document.body, this._portalElement);
            }
            this._portalElement.addEventListener('portalAttached', (e) => {
                resolve(e.detail.projection);
            }, { once: true });
            RendererServiceLocator.current.appendChild(this._portalElement, element);
        });
    }
}
//# sourceMappingURL=SheetService.js.map