// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Themeable } from '../../../Behaviors/Themeable';
import { Appearanceable } from '../../../Behaviors/Appearanceable';
import { Disableable } from '../../../Behaviors/Disableable';
import { Orientable } from '../../../Behaviors/Orientable';
import { Slottable } from '../../../Behaviors/Slottable';
import { Variantable } from '../../../Behaviors/Variantable';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { CustomElement } from '../../Abstracts/CustomElement';
import { BannerElement } from '../Banner/BannerElement';
import { bannerGroupElementTemplate } from './BannerGroupElementTemplate';
import { bannerGroupElementJoyStyle } from './Themes/BannerGroupElement.Joy';
import { bannerGroupElementMemphisStyle } from './Themes/BannerGroupElement.Memphis';
import { bannerGroupElementCosmopolitanStyle } from './Themes/BannerGroupElement.Cosmopolitan';
import { Emit } from '../../../../Events/Decorators/EmitDecorator';
import { HTMLElementEventEmitter } from '@breadstone/mosaik-elements';
import { KeyboardController } from '../../../Controllers/KeyboardController';
import { Key } from '../../../Types/Key';
import { Orientation } from '../../../Types/Orientation';
import { BannerGroupDismissBehavior } from '../../../Types/BannerGroupDismissBehavior';
import { BannerGroupStacking } from '../../../Types/BannerGroupStacking';
import { RendererServiceLocator } from '../../../../Dom/Renderer';
/**
 * BannerGroup - A container component for managing and presenting multiple `Banner` instances.
 *
 * @description
 * The `BannerGroup` component arranges several `<mosaik-banner>` elements either as a stack or a simple paged
 * (carousel‑like) sequence. When `maxLength` equals `1`, only a single banner is shown and keyboard navigation
 * allows users to move between items. When `maxLength` is greater than `1`, banners are presented in a stacked
 * layout. Looping navigation can be enabled via the `loop` property. (Persistence, dismissal strategies and depth
 * stacking enhancements can be incorporated in future iterations.)
 *
 * @name BannerGroup
 * @element mosaik-banner-group
 * @category Notifications
 *
 * @fires change - Fired when the active index changes.
 *
 * @slot - The default slot for banner items.
 * @slot prev - Slot for a custom previous control.
 * @slot next - Slot for a custom next control.
 *
 * @csspart root - The root wrapper part.
 * @csspart viewport - The viewport that contains the banners.
 * @csspart control-prev - The previous control wrapper.
 * @csspart control-next - The next control wrapper.
 *
 * @cssprop {unknown} --banner-group-gap - Gap between stacked banners.
 * @cssprop {unknown} --banner-group-transition-duration - Transition duration for paging.
 *
 * @dependency {BannerElement} - The Banner element.
 *
 * @example
 * ```html
 * <mosaik-banner-group max-visible="1" loop>
 *   <mosaik-banner header="Update 1"></mosaik-banner>
 *   <mosaik-banner header="Update 2"></mosaik-banner>
 *   <mosaik-banner header="Update 3"></mosaik-banner>
 * </mosaik-banner-group>
 * ```
 *
 * @public
 */
let BannerGroupElement = class BannerGroupElement extends Themeable(Orientable(Disableable(Appearanceable(Variantable(Slottable(CustomElement)))))) {
    // #region Fields
    _change;
    _keyboardController;
    _eventSubscriptions;
    _index;
    _loop;
    _maxLength;
    _dismissBehavior;
    _stacking;
    _reverse;
    _previousIndex;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._change = new HTMLElementEventEmitter(this, 'change');
        this._keyboardController = new KeyboardController(this, { capture: true });
        this._eventSubscriptions = [];
        this._index = 0;
        this._loop = false;
        this._maxLength = 5;
        this._dismissBehavior = BannerGroupDismissBehavior.Remove;
        this._stacking = BannerGroupStacking.Vertical;
        this._reverse = false;
        this._previousIndex = 0;
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `{@link is}` property.
     * The `{@link is}` property represents the natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() { return 'mosaik-banner-group'; }
    /**
     * Gets or sets the `index` property representing the current active (start) index.
     *
     * @public
     * @attr
     */
    get index() { return this._index; }
    set index(value) {
        const banners = this.getSlotAssignmentsOf('', BannerElement);
        const clamped = banners.length === 0 ? 0 : Math.max(0, Math.min(value, banners.length - 1));
        if (clamped !== this._index) {
            const prev = this._index;
            this._previousIndex = prev;
            this._index = clamped;
            this.requestUpdate('index');
            this.emitChange(prev);
        }
    }
    /**
     * Gets or sets the `loop` property enabling circular navigation.
     *
     * @public
     * @attr
     */
    get loop() { return this._loop; }
    set loop(value) {
        if (this._loop !== value) {
            this._loop = value;
            this.requestUpdate('loop');
        }
    }
    /**
     * Gets or sets the `maxLength` property (1 = pager/carousel mode).
     *
     * @public
     * @attr
     */
    get maxLength() { return this._maxLength; }
    set maxLength(value) {
        if (this._maxLength !== value) {
            this._maxLength = value;
            this.requestUpdate('maxLength');
        }
    }
    /**
     * Gets or sets the `dismissBehavior` property defining how dismissed banners are handled.
     * (Future extension point.)
     *
     * @public
     * @attr
     */
    get dismissBehavior() { return this._dismissBehavior; }
    set dismissBehavior(value) {
        if (this._dismissBehavior !== value) {
            this._dismissBehavior = value;
            this.requestUpdate('dismissBehavior');
        }
    }
    /**
     * Gets or sets the `stacking` property controlling vertical or depth stacking.
     *
     * @public
     * @attr
     */
    get stacking() { return this._stacking; }
    set stacking(value) {
        if (this._stacking !== value) {
            this._stacking = value;
            this.requestUpdate('stacking');
        }
    }
    /**
     * Gets or sets the `reverse` property displaying newest banners first when true.
     *
     * @public
     * @attr
     */
    get reverse() { return this._reverse; }
    set reverse(value) {
        if (this._reverse !== value) {
            this._reverse = value;
            this.requestUpdate('reverse');
        }
    }
    /**
     * Returns the total number of banners.
     *
     * @public
     * @readonly
     */
    get length() {
        return this.getSlotAssignmentsOf('', BannerElement).length;
    }
    /**
     * Returns whether previous navigation is possible.
     *
     * @public
     * @readonly
     */
    get canPrev() {
        return this.length > 1 && (this.loop || this.index > 0);
    }
    /**
     * Returns whether next navigation is possible.
     *
     * @public
     * @readonly
     */
    get canNext() {
        return this.length > 1 && (this.loop || this.index < this.length - 1);
    }
    /**
     * Fired when the active index changes.
     *
     * @public
     */
    get change() {
        return this._change;
    }
    // #endregion
    // #region Methods
    /**
     * @protected
     * @override
     */
    onSlotChanges() {
        this.clearSubscriptions();
        this.getSlotAssignmentsOf('', BannerElement).forEach((b) => b.classList.add('banner-group-item'));
        this.index = this._index; // clamp to valid range
        this.updateLayout();
    }
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this._keyboardController
            .add(Key.LeftArrow, () => this.previous(), () => !this.disabled && this.canPrev && this.orientation === Orientation.Horizontal, { preventDefault: true })
            .add(Key.RightArrow, () => this.next(), () => !this.disabled && this.canNext && this.orientation === Orientation.Horizontal, { preventDefault: true })
            .add(Key.UpArrow, () => this.previous(), () => !this.disabled && this.canPrev && this.orientation === Orientation.Vertical, { preventDefault: true })
            .add(Key.DownArrow, () => this.next(), () => !this.disabled && this.canNext && this.orientation === Orientation.Vertical, { preventDefault: true })
            .add(Key.Home, () => this.first(), () => !this.disabled && this.length > 0, { preventDefault: true })
            .add(Key.End, () => this.last(), () => !this.disabled && this.length > 0, { preventDefault: true });
    }
    /**
     * Navigates to the previous banner.
     *
     * @public
     */
    previous() {
        this.navigate(-1);
    }
    /**
     * Navigates to the next banner.
     *
     * @public
     */
    next() {
        this.navigate(1);
    }
    /**
     * Navigates to the first banner.
     *
     * @public
     */
    first() {
        this.index = 0;
    }
    /**
     * Navigates to the last banner.
     *
     * @public
     */
    last() {
        this.index = Math.max(0, this.length - 1);
    }
    /**
     * Navigates to a specific index.
     *
     * @public
     */
    goTo(i) {
        this.index = i;
    }
    onIndexPropertyChanged() {
        this.updateLayout();
    }
    onMaxLengthPropertyChanged() {
        this.updateLayout();
    }
    onReversePropertyChanged() {
        this.updateLayout();
    }
    onStackingPropertyChanged() {
        this.updateLayout();
    }
    onOrientationPropertyChanged() {
        this.updateLayout();
    }
    // @Watch('appearance')
    // protected onAppearancePropertyChanged(_prev?: Appearance, next?: Appearance): void {
    //     this.getSlotAssignmentsOf('', BannerElement)
    //         .filter((x) => x.appearance !== 'default')
    //         .forEach((b) => b.appearance = next ?? this.appearance);
    // }
    // @Watch('variant')
    // protected onVariantPropertyChanged(_prev?: Variant, next?: Variant): void {
    //     this.getSlotAssignmentsOf('', BannerElement)
    //         .filter((x) => x.variant !== 'default')
    //         .forEach((b) => b.variant = next ?? this.variant);
    // }
    onDirPropertyChanged() {
        this.getSlotAssignmentsOf('', BannerElement)
            .forEach((b) => b.dir = this.dir);
    }
    onDisabledPropertyChanged(_prev, next) {
        if (next !== undefined) {
            this.getSlotAssignmentsOf('', BannerElement)
                .forEach((b) => b.disabled = next);
        }
    }
    navigate(delta) {
        if (this.disabled || this.length === 0) {
            return;
        }
        let next = this.index + delta;
        if (this.loop && this.length > 0) {
            next = (next + this.length) % this.length;
        }
        this.index = next;
    }
    emitChange(previousIndex) {
        this._change.emit({
            index: this._index,
            previousIndex,
            length: this.length
        });
    }
    clearSubscriptions() {
        this._eventSubscriptions.forEach((x) => x.dispose());
        this._eventSubscriptions = [];
    }
    /**
     * Updates banner visibility, ordering and (optional) depth stacking transforms.
     *
     * @private
     */
    updateLayout() {
        const banners = this.getSlotAssignmentsOf('', BannerElement);
        if (banners.length === 0) {
            return;
        }
        const maxLength = Math.max(1, this.maxLength);
        const isPager = maxLength === 1;
        const start = this.index;
        const endExclusive = isPager ? start + 1 : Math.min(banners.length, start + maxLength);
        const ordered = this._reverse ? [...banners].reverse() : banners;
        // For depth stacking we need the number of visible banners to compute layering order via CSS vars
        if (!isPager && this._stacking === BannerGroupStacking.Depth) {
            RendererServiceLocator.current.setStyle(this, '--banner-group-visible-count', Math.min(maxLength, banners.length));
        }
        else {
            RendererServiceLocator.current.setStyle(this, '--banner-group-visible-count', '');
        }
        ordered.forEach((banner) => {
            const dataIndex = banners.indexOf(banner);
            // Expose the absolute item index as CSS variable (independent of visibility)
            RendererServiceLocator.current.setStyle(banner, '--banner-group-item-index', dataIndex);
            if (isPager) {
                const isActive = dataIndex === start;
                const wasActive = dataIndex === this._previousIndex;
                // Clear any previous depth offsets in pager mode
                RendererServiceLocator.current.setStyle(banner, '--banner-group-item-depth-offset', '');
                // Ensure banner participates in animation lifecycle
                banner.removeAttribute('hidden');
                banner.removeAttribute('inert');
                banner.toggleAttribute('data-active', isActive);
                if (isActive) {
                    banner.setAttribute('aria-hidden', 'false');
                    banner.removeAttribute('data-leaving');
                }
                else if (wasActive) {
                    // Leaving state: keep visible for transition, then hide after transition end
                    banner.setAttribute('data-leaving', 'true');
                    banner.setAttribute('aria-hidden', 'true');
                    this.attachLeavingTransitionHandler(banner);
                }
                else {
                    // Inactive & not part of current transition: hide immediately
                    banner.setAttribute('aria-hidden', 'true');
                    banner.setAttribute('hidden', '');
                    banner.removeAttribute('data-leaving');
                    banner.removeAttribute('data-active');
                }
                return; // skip non‑pager logic
            }
            // Non‑pager (stack / depth) logic
            const visible = dataIndex >= start && dataIndex < endExclusive;
            banner.toggleAttribute('hidden', !visible);
            if (!visible) {
                banner.setAttribute('aria-hidden', 'true');
                banner.setAttribute('inert', '');
                banner.removeAttribute('data-active');
                RendererServiceLocator.current.setStyle(banner, '--banner-group-item-depth-offset', '');
            }
            else {
                banner.setAttribute('aria-hidden', 'false');
                banner.removeAttribute('inert');
                const offset = dataIndex - start;
                if (this._stacking === BannerGroupStacking.Depth) {
                    RendererServiceLocator.current.setStyle(banner, '--banner-group-item-depth-offset', offset);
                }
                else {
                    RendererServiceLocator.current.setStyle(banner, '--banner-group-item-depth-offset', 0);
                }
                if (offset === 0) {
                    banner.setAttribute('data-active', 'true');
                }
                else {
                    banner.removeAttribute('data-active');
                }
            }
        });
    }
    /**
     * Attaches a one‑time transition end handler to a leaving banner (pager mode) to finalize its hidden state.
     * @private
     */
    attachLeavingTransitionHandler(banner) {
        const handler = (ev) => {
            if (ev.target !== banner) {
                return;
            }
            banner.setAttribute('hidden', '');
            banner.removeAttribute('data-leaving');
            banner.removeEventListener('transitionend', handler);
            banner.removeEventListener('animationend', handler);
        };
        // Re-attach (idempotent) by removing prior handlers first
        banner.removeEventListener('transitionend', handler);
        banner.removeEventListener('animationend', handler);
        banner.addEventListener('transitionend', handler, { once: true });
        banner.addEventListener('animationend', handler, { once: true });
    }
};
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], BannerGroupElement.prototype, "index", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], BannerGroupElement.prototype, "loop", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], BannerGroupElement.prototype, "maxLength", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], BannerGroupElement.prototype, "dismissBehavior", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], BannerGroupElement.prototype, "stacking", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], BannerGroupElement.prototype, "reverse", null);
__decorate([
    Emit({ eventName: 'change' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], BannerGroupElement.prototype, "change", null);
__decorate([
    Watch('index'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onIndexPropertyChanged", null);
__decorate([
    Watch('maxLength'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onMaxLengthPropertyChanged", null);
__decorate([
    Watch('reverse'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onReversePropertyChanged", null);
__decorate([
    Watch('stacking'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onStackingPropertyChanged", null);
__decorate([
    Watch('orientation'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onOrientationPropertyChanged", null);
__decorate([
    Watch('dir'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onDirPropertyChanged", null);
__decorate([
    Watch('disabled'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Boolean, Boolean]),
    __metadata("design:returntype", void 0)
], BannerGroupElement.prototype, "onDisabledPropertyChanged", null);
BannerGroupElement = __decorate([
    Component({
        selector: 'mosaik-banner-group',
        template: bannerGroupElementTemplate,
        themes: {
            joy: bannerGroupElementJoyStyle,
            memphis: bannerGroupElementMemphisStyle,
            cosmopolitan: bannerGroupElementCosmopolitanStyle
        },
        host: { role: 'region' },
        imports: [BannerElement]
    }),
    __metadata("design:paramtypes", [])
], BannerGroupElement);
export { BannerGroupElement };
//# sourceMappingURL=BannerGroupElement.js.map