// #region Imports
import { fadeSlideIn, fadeSlideOut } from '../../../../Animations/Presets/Fade';
import { RendererServiceLocator } from '../../../../Dom/Renderer';
import { on, waitForEvent } from '@breadstone/mosaik-elements';
import { EID } from '../../../../Utilities/EId';
import { ToastPosition } from '../../../Types/ToastPosition';
import { Variant } from '../../../Types/Variant';
import { PortalElement } from '../../Primitives/Portal/PortalElement';
import { ToastElement } from './ToastElement';
import { TOAST_SERVICE_DEFAULT_CONFIG } from './IToastServiceConfig';
/**
 * Represents the `Toast` constant.
 *
 * @public
 */
export var Toast;
(function (Toast) {
    let Timeout;
    (function (Timeout) {
        /**
         * The duration of a short (1500ms) toast message.
         *
         * @public
         * @readonly
         * @static
         */
        Timeout.SHORT = 1500;
        /**
         * The duration of a medium (3000ms) toast message.
         *
         * @public
         * @readonly
         * @static
         */
        Timeout.MEDIUM = 3000;
        /**
         * The duration of a long (6000ms) toast message.
         *
         * @public
         * @readonly
         * @static
         */
        Timeout.LONG = 6000;
    })(Timeout = Toast.Timeout || (Toast.Timeout = {}));
})(Toast || (Toast = {}));
/**
 * Represents the `TOAST_DEFAULT_OPTIONS` constant.
 *
 * @private
 */
const TOAST_DEFAULT_OPTIONS = {
    header: '',
    content: '',
    timeout: Toast.Timeout.MEDIUM,
    position: ToastPosition.Bottom,
    showProgress: false,
    hasBackdrop: false,
    pressEscapeToClose: true,
    clickOutsideToClose: true,
    variant: Variant.Default,
    formatter: null,
    closeable: false,
    isBusy: false,
    enter: fadeSlideIn,
    exit: fadeSlideOut
};
/**
 * Service for managing the display and behavior of toast elements.
 * Provides methods to open and close toast elements within a portal structure.
 *
 * Uses a singleton pattern with static configuration.
 *
 * @example
 * ```typescript
 * // Configure once at app startup
 * ToastService.configure({
 *     closeOnNavigation: true,
 *     behaviors: []
 * });
 *
 * // Use throughout the app
 * await ToastService.instance.open({
 *     header: 'Success',
 *     content: 'Operation completed successfully',
 *     timeout: Toast.Timeout.SHORT
 * });
 * ```
 *
 * @public
 */
export class ToastService {
    // #region Fields
    static _instance = null;
    static _config = TOAST_SERVICE_DEFAULT_CONFIG;
    _toastMap;
    _behaviors;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `ToastService` class.
     * Use `ToastService.configure()` and `ToastService.instance` instead of direct instantiation.
     *
     * @private
     */
    constructor(config) {
        this._toastMap = new Map();
        this._behaviors = config.behaviors ?? [];
        if (config.closeOnNavigation !== false) {
            on(window, 'popstate', () => {
                if (this._toastMap.size > 0) {
                    for (const [key, value] of this._toastMap) {
                        if (value.options?.navigateToClose) {
                            void this.close(key);
                        }
                    }
                }
            });
        }
    }
    // #endregion
    // #region Properties
    /**
     * Gets the singleton instance of `ToastService`.
     * If not configured, uses default configuration.
     *
     * @public
     * @static
     */
    static get instance() {
        ToastService._instance ??= new ToastService(ToastService._config);
        return ToastService._instance;
    }
    // #endregion
    // #region Methods
    /**
     * Configures the `ToastService` 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 toast service.
     * @throws Error if already configured.
     *
     * @example
     * ```typescript
     * ToastService.configure({
     *     closeOnNavigation: true,
     *     behaviors: [myCustomBehavior]
     * });
     * ```
     */
    static configure(config) {
        ToastService._config = {
            ...TOAST_SERVICE_DEFAULT_CONFIG,
            ...config
        };
    }
    /**
     * Opens a toast within a portal.
     *
     * @public
     * @template TResult - Expected return type of the resolved toast result.
     * @param options - Properties for portal and toast.
     * @returns A promise resolving with the toast result.
     */
    async open(options) {
        // Allow behaviors to configure options
        const mergedOptions = { ...options };
        for (const behavior of this._behaviors) {
            behavior.configure(mergedOptions);
        }
        const toastId = mergedOptions.id ?? EID.next(ToastElement.is);
        let toastRef = this._toastMap.get(toastId);
        if (!toastRef) {
            const toastElement = RendererServiceLocator.current.createElement(ToastElement.is);
            RendererServiceLocator.current.setProperty(toastElement, 'id', toastId);
            const [portal, portalProjection] = await this.extendToPortal(toastElement, mergedOptions);
            this._toastMap.set(toastId, toastRef = {
                toastId: toastId,
                toastElement: toastElement,
                portalElement: portal,
                portalProjectionElement: portalProjection,
                options: mergedOptions
            });
        }
        // Destructure to exclude isOpen from options - we control open state via toastElement.open()
        // This ensures pressEscapeToClose and other properties are set before the escape listener is attached
        const { isOpen: _ignored, ...safeOptions } = mergedOptions;
        Object.assign(toastRef.toastElement, {
            ...TOAST_DEFAULT_OPTIONS,
            ...safeOptions
        });
        // Notify behaviors about attachment
        for (const behavior of this._behaviors) {
            behavior.attach(toastRef);
        }
        return new Promise((resolve) => {
            toastRef.resolve = resolve;
            void toastRef.toastElement.open();
            void waitForEvent(toastRef.toastElement, 'closed').then(() => {
                // Notify behaviors about detachment
                for (const behavior of this._behaviors) {
                    behavior.detach(toastRef);
                }
                this._toastMap.delete(toastId);
                toastRef.toastElement.remove();
                toastRef.portalElement.remove();
                void toastRef.portalProjectionElement.close();
                toastRef.portalProjectionElement.remove();
                if (toastRef.resolve) {
                    toastRef.resolve();
                }
            });
        });
    }
    /**
     * Closes the toast and removes it from the DOM.
     *
     * @public
     * @template TResult - Type of result returned when the toast is closed.
     * @param toastId - Unique identifier for the toast instance.
     * @param result - Optional result returned when closing the toast.
     * @returns A promise resolving once the toast is fully closed.
     */
    async close(toastId, result) {
        return new Promise((resolve) => {
            const ref = this._toastMap.get(toastId);
            if (ref) {
                this._toastMap.delete(toastId);
                void waitForEvent(ref.toastElement, '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.toastElement.close();
            }
            else {
                console.error(`Wrapper with ID "${toastId}" 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 toasts
        for (const [id] of this._toastMap) {
            void this.close(id);
        }
        this._toastMap.clear();
    }
    /**
     * Extends the toast 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 PortalElement and PortalProjectionElement containing the toast.
     */
    extendToPortal(element, props) {
        const portal = this.createPortal(props);
        return new Promise((resolve) => {
            on(portal, 'portalAttached', (e) => {
                resolve([e.target, e.detail.projection]);
            });
            RendererServiceLocator.current.appendChild(portal, element);
        });
    }
    /**
     * Creates a new portal element and appends it to the document body.
     *
     * @private
     * @param props - Optional properties to set on the portal element.
     * @returns The created PortalElement.
     */
    createPortal(props) {
        const portal = RendererServiceLocator.current.createElement(PortalElement.is);
        Object.assign(portal, props);
        RendererServiceLocator.current.setAttribute(portal, 'id', EID.next(PortalElement.is));
        RendererServiceLocator.current.appendChild(document.body, portal);
        return portal;
    }
}
//# sourceMappingURL=ToastService.js.map