// #region Imports
import { RendererServiceLocator } from '../../../../../Dom/Renderer';
import { DRAWER_STACK_BEHAVIOR_DEFAULT_CONFIG } from './IDrawerStackBehaviorConfig';
// #endregion
/**
 * CSS class name applied to drawers that are stacked (have another drawer on top).
 *
 * @private
 */
const DRAWER_STACKED_CLASS = 'mosaik-drawer-stacked';
/**
 * Behavior that manages a stack of drawers with decreasing widths.
 * Each drawer in the stack is offset from the previous one, creating
 * a cascading visual effect.
 *
 * @public
 */
export class DrawerStackBehavior {
    // #region Fields
    _config;
    _stack;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `DrawerStackBehavior` class.
     *
     * @public
     * @param config - Optional configuration for stack behavior.
     */
    constructor(config) {
        this._config = {
            ...DRAWER_STACK_BEHAVIOR_DEFAULT_CONFIG,
            ...config
        };
        this._stack = new Array();
    }
    // #endregion
    // #region Properties
    /**
     * Returns the current number of drawers in the stack.
     *
     * @public
     * @readonly
     */
    get stackSize() {
        return this._stack.length;
    }
    /**
     * Returns the current stack configuration.
     *
     * @public
     * @readonly
     */
    get config() {
        return this._config;
    }
    /**
     * Returns an array of all drawer references in stack order (oldest first).
     *
     * @public
     * @readonly
     */
    get stack() {
        return this._stack;
    }
    // #endregion
    // #region Methods
    /**
     * Configures the drawer options before opening.
     * Calculates the appropriate width based on stack position.
     *
     * @public
     * @param options - The drawer options to configure.
     */
    configure(_options) {
        // Check max stack depth
        if (this._config.maxStackDepth !== undefined && this._stack.length >= this._config.maxStackDepth) {
            throw new Error(`Cannot open drawer: maximum stack depth (${this._config.maxStackDepth}) reached.`);
        }
    }
    /**
     * Attaches a drawer to the stack and applies stack styling.
     *
     * @public
     * @param ref - The drawer reference to attach.
     */
    attach(ref) {
        // Use width from options if provided, otherwise fall back to config.baseWidth
        const baseWidth = this.cssLengthToString(ref.options?.width) ?? this._config.baseWidth;
        // Grow smaller drawers to match the new larger drawer
        this.growSmallerDrawersToMatchLargest(baseWidth);
        // Add to stack FIRST, so applyStackedClasses correctly identifies the topmost drawer
        this._stack.push(ref);
        // Add stacked class to previous drawers (now correctly applied after the new drawer is in the stack)
        this.applyStackedClasses();
    }
    /**
     * Detaches a drawer from the stack.
     *
     * @public
     * @param ref - The drawer reference to detach.
     */
    detach(ref) {
        const index = this._stack.indexOf(ref);
        if (index !== -1) {
            this._stack.splice(index, 1);
            // Recalculate widths for remaining drawers
            this.recalculateStackWidths();
            // Update stacked classes
            this.applyStackedClasses();
        }
    }
    /**
     * Disposes of all resources and clears the stack.
     *
     * @public
     */
    dispose() {
        this._stack.length = 0;
    }
    /**
     * Calculates the width for a drawer at a specific stack level.
     *
     * @public
     * @param stackLevel - The zero-based index of the drawer in the stack.
     * @param baseWidthOverride - Optional override for the base width.
     * @returns The calculated CSS width string.
     */
    calculateWidth(stackLevel, baseWidthOverride) {
        const baseWidth = baseWidthOverride ?? this._config.baseWidth;
        const reduction = stackLevel * this._config.stackThreshold;
        // Parse the base width to determine format (%, px, vw, etc.)
        const match = (/^(\d+(?:\.\d+)?)(%.*)$/).exec(baseWidth);
        if (match) {
            const numericValue = parseFloat(match[1]);
            const unit = match[2];
            const calculatedValue = Math.max(0, numericValue - reduction);
            return `${calculatedValue}${unit}`;
        }
        // For calc(), vw, or other complex values, use CSS calc
        return `calc(${baseWidth} - ${reduction}%)`;
    }
    /**
     * Applies the stacked CSS class to all drawers except the topmost one.
     *
     * @private
     */
    applyStackedClasses() {
        for (let i = 0; i < this._stack.length; i++) {
            const ref = this._stack[i];
            const isTopmost = i === this._stack.length - 1;
            if (isTopmost) {
                RendererServiceLocator.current.removeClass(ref.drawerElement, DRAWER_STACKED_CLASS);
            }
            else {
                RendererServiceLocator.current.addClass(ref.drawerElement, DRAWER_STACKED_CLASS);
            }
        }
    }
    /**
     * Recalculates and applies widths for all remaining drawers in the stack.
     * After a drawer is closed, we need to restore each drawer to its original width
     * (which may have been grown when larger drawers were opened on top).
     *
     * @private
     */
    recalculateStackWidths() {
        // Find the largest base width among remaining drawers
        let largestBaseWidth = 0;
        for (const ref of this._stack) {
            const baseWidth = this.cssLengthToString(ref.options?.width) ?? this._config.baseWidth;
            const percentage = this.parseWidthToPercentage(baseWidth);
            if (percentage !== null && percentage > largestBaseWidth) {
                largestBaseWidth = percentage;
            }
        }
        // Recalculate widths: drawers should be based on the largest remaining drawer
        const threshold = this._config.stackThreshold;
        const stackLength = this._stack.length;
        for (let i = 0; i < stackLength; i++) {
            const ref = this._stack[i];
            const baseWidth = this.cssLengthToString(ref.options?.width) ?? this._config.baseWidth;
            const basePercentage = this.parseWidthToPercentage(baseWidth);
            if (basePercentage === null) {
                // For non-percentage widths, fall back to the original behavior
                const newWidth = this.calculateWidth(i, baseWidth);
                this.applyStackStyles(ref, i, newWidth);
                continue;
            }
            // If the largest remaining drawer is bigger than this drawer's base,
            // grow this drawer to match (plus offset)
            if (largestBaseWidth > basePercentage) {
                const offsetFromTop = stackLength - 1 - i;
                const grownWidth = largestBaseWidth + (offsetFromTop * threshold);
                this.applyStackStyles(ref, i, `${grownWidth}%`);
            }
            else {
                // Otherwise use the original calculated width based on stack position
                const newWidth = this.calculateWidth(i, baseWidth);
                this.applyStackStyles(ref, i, newWidth);
            }
        }
    }
    /**
     * Converts a CssLength to a string representation.
     * If the value is a number, it's treated as pixels and not suitable for percentage-based calculations.
     *
     * @private
     * @param value - The CssLength value to convert.
     * @returns The string representation, or undefined if it's a number (pixels).
     */
    cssLengthToString(value) {
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
        if (value === undefined || value === null) {
            return undefined;
        }
        if (typeof value === 'number') {
            // Numbers are treated as pixels, which we can't use for percentage-based stack calculations
            return undefined;
        }
        return value;
    }
    /**
     * Parses a percentage-based width string into a numeric value.
     *
     * @private
     * @param width - The CSS width string (e.g. "50%").
     * @returns The numeric percentage value, or null if not a percentage.
     */
    parseWidthToPercentage(width) {
        if (!width) {
            return null;
        }
        const match = (/^(\d+(?:\.\d+)?)%$/).exec(width);
        if (match) {
            return parseFloat(match[1]);
        }
        return null;
    }
    /**
     * Grows smaller drawers in the stack to match the size of a newly opened larger drawer.
     * Each existing drawer gets the new base width plus an offset based on its distance from the top.
     *
     * @private
     * @param newDrawerBaseWidth - The base width of the newly opened drawer.
     */
    growSmallerDrawersToMatchLargest(newDrawerBaseWidth) {
        const newWidthPercentage = this.parseWidthToPercentage(newDrawerBaseWidth);
        if (newWidthPercentage === null) {
            return;
        }
        const threshold = this._config.stackThreshold;
        const stackLength = this._stack.length;
        for (let i = 0; i < stackLength; i++) {
            const ref = this._stack[i];
            const existingBaseWidth = this.cssLengthToString(ref.options?.width) ?? this._config.baseWidth;
            const existingWidthPercentage = this.parseWidthToPercentage(existingBaseWidth);
            if (existingWidthPercentage === null) {
                continue;
            }
            // Only grow if the new drawer is larger
            if (newWidthPercentage > existingWidthPercentage) {
                // Calculate offset from the top of the stack (new drawer will be at stackLength)
                // Drawer at index i has (stackLength - i) drawers above it
                const offsetFromTop = stackLength - i;
                const grownWidth = newWidthPercentage + (offsetFromTop * threshold);
                const grownWidthStr = `${grownWidth}%`;
                // Update computed width and apply styles
                this.applyStackStyles(ref, i, grownWidthStr);
            }
        }
    }
    /**
     * Calculates the z-index for a drawer at a specific stack level.
     *
     * @private
     * @param stackLevel - The zero-based index of the drawer in the stack.
     * @returns The calculated z-index value.
     */
    calculateZIndex(stackLevel) {
        const baseZIndex = 1000;
        return baseZIndex + (stackLevel * this._config.zIndexIncrement);
    }
    /**
     * Applies stack-related styles to a drawer element.
     *
     * @private
     * @param ref - The drawer reference.
     * @param stackLevel - The stack level for this drawer.
     * @param computedWidth - The computed width for this drawer.
     */
    applyStackStyles(ref, stackLevel, computedWidth) {
        // Use data attributes for CSS styling hooks
        RendererServiceLocator.current.setAttribute(ref.drawerElement, 'data-stack-level', String(stackLevel));
        RendererServiceLocator.current.setAttribute(ref.drawerElement, 'data-computed-width', computedWidth);
        // Apply CSS custom properties for stack styling
        RendererServiceLocator.current.setProperty(ref.drawerElement, '--drawer-stack-level', String(stackLevel));
        RendererServiceLocator.current.setProperty(ref.drawerElement, '--drawer-computed-width', computedWidth);
        RendererServiceLocator.current.setProperty(ref.drawerElement, '--drawer-z-index', String(this.calculateZIndex(stackLevel)));
        // Apply stack indicator offset if enabled
        if (this._config.showStackIndicator && stackLevel > 0) {
            const indicatorOffset = stackLevel * this._config.stackIndicatorOffset;
            RendererServiceLocator.current.setProperty(ref.drawerElement, '--drawer-stack-indicator-offset', `${indicatorOffset}px`);
        }
        // Apply width directly via the component's width property
        ref.drawerElement.width = computedWidth;
    }
}
//# sourceMappingURL=DrawerStackBehavior.js.map