// #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 { Appearanceable } from '../../../Behaviors/Appearanceable';
import { Disableable } from '../../../Behaviors/Disableable';
import { Themeable } from '../../../Behaviors/Themeable';
import { Variantable } from '../../../Behaviors/Variantable';
import { addAutoplayController } from '../../../Controllers/AutoplayController';
import { addInheritanceProviderController } from '../../../Controllers/InheritanceProviderController';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { CarouselDisplayMode } from '../../../Types/CarouselDisplayMode';
import { CarouselNavigationPosition } from '../../../Types/CarouselNavigationPosition';
import { Key } from '../../../Types/Key';
import { FloatingActionButtonElement } from '../../Buttons/FloatingActionButton/FloatingActionButtonElement';
import { SelectorElement } from '../Abstracts/Selector/SelectorElement';
import { carouselElementTemplate } from './CarouselElementTemplate';
import { CAROUSEL_INHERITANCE_CONTEXT } from './CarouselInheritanceContext';
import { CarouselItemElement } from './CarouselItemElement';
import { carouselElementCosmopolitanStyle } from './Themes/CarouselElement.Cosmopolitan';
import { carouselElementJoyStyle } from './Themes/CarouselElement.Joy';
import { carouselElementMemphisStyle } from './Themes/CarouselElement.Memphis';
// #endregion
/**
 * Carousel - A content navigation component that displays items in a horizontally scrolling slideshow format.
 *
 * Enables users to browse through multiple content items (images, cards, media) in a space-efficient
 * carousel layout with navigation controls. Supports automatic cycling, manual navigation, and
 * touch/swipe interactions for engaging content presentation. Offers two display modes: `slide`
 * (horizontal track) and `deck` (stacked cards with depth effect). Navigation button placement
 * can be configured via `navigationPosition` (`split`, `inside`, `outside`).
 *
 * @name CarouselElement
 * @element mosaik-carousel
 * @category Selectors
 *
 * @slot - Default content area for carousel items and navigation elements
 * @slot indicator - The indicator slot.
 *
 * @csspart content - Content area wrapping the item list or deck
 * @csspart indicator - Page indicator showing current position
 * @csspart next - Navigation button to go to the next item
 * @csspart previous - Navigation button to go to the previous item
 *
 * @cssprop {String} --carousel-deck-animation-duration - Transition duration for deck card animations
 * @cssprop {String} --carousel-deck-rotation - Rotation angle for pile cards (design token)
 * @cssprop {String} --carousel-font-family - The font family CSS custom property.
 * @cssprop {String} --carousel-font-letter-spacing - The font letter spacing CSS custom property.
 * @cssprop {String} --carousel-font-line-height - The font line height CSS custom property.
 * @cssprop {String} --carousel-font-size - The font size CSS custom property.
 * @cssprop {String} --carousel-font-text-decoration - The font text decoration CSS custom property.
 * @cssprop {String} --carousel-font-text-transform - The font text transform CSS custom property.
 * @cssprop {String} --carousel-font-weight - The font weight CSS custom property.
 * @cssprop {String} --carousel-gap - Spacing between carousel items
 * @cssprop {String} --carousel-padding-bottom - The padding bottom CSS custom property.
 * @cssprop {String} --carousel-padding-left - The padding left CSS custom property.
 * @cssprop {String} --carousel-padding-right - The padding right CSS custom property.
 * @cssprop {String} --carousel-padding-top - The padding top CSS custom property.
 * @cssprop {String} --carousel-shadow - The shadow CSS custom property.
 * @cssprop {String} --carousel-shadow-blur - The shadow blur CSS custom property.
 * @cssprop {String} --carousel-shadow-color - The shadow color CSS custom property.
 * @cssprop {String} --carousel-shadow-offset-x - The shadow offset x CSS custom property.
 * @cssprop {String} --carousel-shadow-offset-y - The shadow offset y CSS custom property.
 * @cssprop {String} --carousel-shadow-spread - The shadow spread CSS custom property.
 * @cssprop {String} --carousel-transition-duration - Animation duration for slide-mode item transitions
 * @cssprop {String} --carousel-transition-mode - The transition mode CSS custom property.
 * @cssprop {String} --carousel-transition-property - The transition property CSS custom property.
 * @cssprop {String} --carousel-translate - The translate CSS custom property.
 *
 * @dependency mosaik-fab - The Fab element.
 * @dependency mosaik-text - The Text element.
 *
 * @example
 * ```html
 * <!-- Slide carousel with outside navigation -->
 * <mosaik-carousel display-mode="slide" navigation-position="outside" show-navigation>
 *   <mosaik-carousel-item>
 *     <mosaik-image src="slide1.jpg" alt="Slide 1"></mosaik-image>
 *   </mosaik-carousel-item>
 *   <mosaik-carousel-item>
 *     <mosaik-image src="slide2.jpg" alt="Slide 2"></mosaik-image>
 *   </mosaik-carousel-item>
 * </mosaik-carousel>
 *
 * <!-- Deck carousel (Tinder-like stacked cards) -->
 * <mosaik-carousel display-mode="deck" show-navigation>
 *   <mosaik-carousel-item>
 *     <mosaik-card>Card 1</mosaik-card>
 *   </mosaik-carousel-item>
 *   <mosaik-carousel-item>
 *     <mosaik-card>Card 2</mosaik-card>
 *   </mosaik-carousel-item>
 *   <mosaik-carousel-item>
 *     <mosaik-card>Card 3</mosaik-card>
 *   </mosaik-carousel-item>
 * </mosaik-carousel>
 * ```
 *
 * @public
 *
 * @keyhandle LeftArrow - Navigates to the previous item in the carousel
 * @keyhandle RightArrow - Navigates to the next item in the carousel
 * @keyhandle Home - Navigates to the first item in the carousel
 * @keyhandle End - Navigates to the last item in the carousel
 */
let CarouselElement = class CarouselElement extends Themeable(Disableable(Appearanceable(Variantable(SelectorElement)))) {
    // #region Fields
    _listElement;
    _position;
    _hasPrevious;
    _hasNext;
    _loop;
    _showNavigation;
    _showIndicator;
    _navigationPosition;
    _displayMode;
    _deckAnimating;
    _autoplay;
    _timeout;
    _autoplayController;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        addInheritanceProviderController(this, {
            context: CAROUSEL_INHERITANCE_CONTEXT,
            values: () => ({
                disabled: this.disabled
            })
        });
        this._listElement = null;
        this._position = 0;
        this._hasPrevious = false;
        this._hasNext = false;
        this._loop = false;
        this._showNavigation = false;
        this._showIndicator = false;
        this._navigationPosition = CarouselNavigationPosition.Inside;
        this._displayMode = CarouselDisplayMode.Slide;
        this._deckAnimating = false;
        this._autoplay = false;
        this._timeout = 0;
        this._autoplayController = addAutoplayController(this, {
            onTimerTick: () => this.selectNext(),
            getDuration: () => this.getEffectiveTimeout(),
            getHoverElement: () => this._listElement,
            isEnabled: () => this._autoplay && this._timeout > 0 && this.items.length >= 2
        });
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     * The `is` property represents natural name of this element.
     *
     * @public
     * @static
     * @override
     * @readonly
     */
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    static get is() {
        return 'mosaik-carousel';
    }
    /**
     * Gets or sets the `loop` property.
     *
     * @public
     * @attr
     */
    get loop() {
        return this._loop;
    }
    set loop(value) {
        if (this._loop !== value) {
            this._loop = value;
            this.requestUpdate('loop');
        }
    }
    /**
     * Gets or sets the `showNavigation` property.
     *
     * @public
     * @attr
     */
    get showNavigation() {
        return this._showNavigation;
    }
    set showNavigation(value) {
        if (this._showNavigation !== value) {
            this._showNavigation = value;
            this.requestUpdate('showNavigation');
        }
    }
    /**
     * Gets or sets the `showIndicator` property.
     *
     * @public
     * @attr
     */
    get showIndicator() {
        return this._showIndicator;
    }
    set showIndicator(value) {
        if (this._showIndicator !== value) {
            this._showIndicator = value;
            this.requestUpdate('showIndicator');
        }
    }
    /**
    * Gets or sets the `hasPrevious` property.
    *
    * @public
    * @attr
    * @readonly
    */
    get hasPrevious() {
        return this._hasPrevious;
    }
    set hasPrevious(value) {
        if (this._hasPrevious !== value) {
            this._hasPrevious = value;
            this.requestUpdate('hasPrevious');
        }
    }
    /**
     * Gets or sets the `hasNext` property.
     *
     * @public
     * @attr
     * @readonly
     */
    get hasNext() {
        return this._hasNext;
    }
    set hasNext(value) {
        if (this._hasNext !== value) {
            this._hasNext = value;
            this.requestUpdate('hasNext');
        }
    }
    /**
     * Gets or sets the `navigationPosition` property.
     * Controls where the navigation buttons are placed relative to the carousel content.
     *
     * @public
     * @attr
     */
    get navigationPosition() {
        return this._navigationPosition;
    }
    set navigationPosition(value) {
        if (this._navigationPosition !== value) {
            this._navigationPosition = value;
            this.requestUpdate('navigationPosition');
        }
    }
    /**
     * Gets or sets the `displayMode` property.
     * Determines how carousel items are arranged and transitioned.
     * Use `slide` for a traditional horizontal track or `deck` for stacked card presentation.
     *
     * @public
     * @attr
     */
    get displayMode() {
        return this._displayMode;
    }
    set displayMode(value) {
        if (this._displayMode !== value) {
            this._displayMode = value;
            this.requestUpdate('displayMode');
        }
    }
    /**
     * Gets or sets the `autoplay` property.
     * Enables or disables automatic carousel cycling.
     *
     * @public
     * @attr
     */
    get autoplay() {
        return this._autoplay;
    }
    set autoplay(value) {
        if (this._autoplay !== value) {
            this._autoplay = value;
            this.requestUpdate('autoplay');
        }
    }
    /**
     * Gets or sets the `timeout` property.
     * The timeout in milliseconds between automatic carousel item transitions.
     *
     * @public
     * @attr
     */
    get timeout() {
        return this._timeout;
    }
    set timeout(value) {
        if (this._timeout !== value) {
            this._timeout = value;
            this.requestUpdate('timeout');
        }
    }
    /**
     * Gets the items of the Carousel.
     *
     * @remark
     * It is important to override the base class property to ensure that only
     * `CarouselItemElement` instances are returned.
     *
     * @public
     * @readonly
     * @ignore
     */
    get items() {
        return super.items;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this.keyboard
            .add([Key.LeftArrow], () => this.selectPrevious())
            .add([Key.RightArrow], () => this.selectNext());
    }
    /**
     * Starts autoplay.
     *
     * @public
     */
    start() {
        if (!this._autoplay) {
            return;
        }
        this._autoplayController.start();
    }
    /**
     * Stops autoplay.
     *
     * @public
     */
    stop() {
        if (!this._autoplay) {
            return;
        }
        this._autoplayController.stop();
    }
    /**
     * Pauses autoplay.
     *
     * @public
     */
    pause() {
        if (!this._autoplay) {
            return;
        }
        this._autoplayController.pause();
    }
    /**
     * Resumes autoplay.
     *
     * @public
     */
    resume() {
        if (!this._autoplay) {
            return;
        }
        this._autoplayController.resume();
    }
    /**
     * @public
     * @override
     */
    select(item) {
        super.select(item);
        if (this._displayMode === CarouselDisplayMode.Deck) {
            this.updateDeckLayout();
        }
        else {
            this.scrollToActiveItem();
        }
        this.restartAutoplay();
    }
    /**
     * @public
     * @override
     */
    selectNext() {
        if (this.isOutOfRange(this.selectedIndex, 'next')) {
            this.selectFirst();
        }
        else if (this._displayMode === CarouselDisplayMode.Deck) {
            this.dismissDeckCard('next');
        }
        else {
            super.selectNext();
            this.scrollToActiveItem();
            this.restartAutoplay();
        }
    }
    /**
     * @public
     * @override
     */
    selectPrevious() {
        if (this.isOutOfRange(this.selectedIndex, 'prev')) {
            this.selectLast();
        }
        else if (this._displayMode === CarouselDisplayMode.Deck) {
            this.arriveDeckCard('prev');
        }
        else {
            super.selectPrevious();
            this.scrollToActiveItem();
            this.restartAutoplay();
        }
    }
    /**
     * @public
     * @override
     */
    selectFirst() {
        super.selectFirst();
        if (this._displayMode === CarouselDisplayMode.Deck) {
            this.updateDeckLayout();
        }
        else {
            this.scrollToActiveItem();
        }
        this.restartAutoplay();
    }
    /**
     * @public
     * @override
     */
    selectLast() {
        super.selectLast();
        if (this._displayMode === CarouselDisplayMode.Deck) {
            this.updateDeckLayout();
        }
        else {
            this.scrollToActiveItem();
        }
        this.restartAutoplay();
    }
    /**
     * @protected
     * @override
     */
    onApplyTemplate() {
        super.onApplyTemplate();
        this._listElement = this.getTemplatePart('list', 'token');
    }
    /**
     * @protected
     * @override
     */
    onItemsChanged() {
        super.onItemsChanged();
        const items = this.items.filter((x) => x instanceof CarouselItemElement);
        // here we handle the special logic, when no item is initial isSelected.
        // we select the first item, if no item is selected.
        if (items.length && !items.some((x) => x.isSelected)) {
            items.at(0)?.select(true);
            // set the state of the navigation buttons
            this.hasPrevious = this._loop ? this._loop : false;
            this.hasNext = this._loop ? true : items.length > 1;
        }
        if (this._displayMode === CarouselDisplayMode.Deck) {
            this.updateDeckLayout();
        }
        if (this._autoplay && this._timeout > 0 && items.length >= 2) {
            this._autoplayController.start();
        }
        else {
            this._autoplayController.stop();
        }
    }
    /**
     * Handles autoplay changes.
     *
     * @protected
     */
    onAutoplayChanged() {
        if (this._autoplay && this._timeout > 0 && this.items.length >= 2) {
            this._autoplayController.start();
        }
        else {
            this._autoplayController.stop();
        }
    }
    /**
     * Handles timeout changes.
     *
     * @protected
     */
    onTimeoutChanged() {
        if (this._autoplay && this._timeout > 0 && this.items.length >= 2) {
            this._autoplayController.updateDurationAndRestart(this.getEffectiveTimeout());
        }
        else {
            this._autoplayController.stop();
        }
    }
    /**
     * Called when the `displayMode` property changes.
     * Re-applies the appropriate layout for the new mode.
     *
     * @protected
     */
    onDisplayModeChanged() {
        if (this._displayMode === CarouselDisplayMode.Deck) {
            this.updateDeckLayout();
        }
    }
    /**
     * Resets all deck item attributes to reflect the current selection.
     * Only manages state attributes - all visual presentation is handled in SCSS.
     *
     * @private
     */
    updateDeckLayout() {
        const items = this.items;
        const activeIndex = this.selectedIndex;
        for (let i = 0; i < items.length; i++) {
            const item = items[i];
            item.isDismissed = false;
            item.dismissDirection = null;
            item.isActive = i === activeIndex;
        }
        this.hasPrevious = this._loop ? true : activeIndex > 0;
        this.hasNext = this._loop ? true : activeIndex < items.length - 1;
    }
    /**
     * Dismisses the current top card in the given direction.
     * The card slides off-screen (CSS transition), then snaps back behind the
     * pile and the next card becomes active.
     *
     * @param direction - The direction the card leaves ('next' or 'prev').
     * @private
     */
    dismissDeckCard(direction) {
        if (this._deckAnimating) {
            return;
        }
        this._deckAnimating = true;
        const leavingCard = this.items[this.selectedIndex];
        leavingCard.dismissDirection = direction;
        leavingCard.isDismissed = true;
        const onTransitionEnd = () => {
            leavingCard.removeEventListener('transitionend', onTransitionEnd);
            leavingCard.setAttribute('no-transition', '');
            super.selectNext();
            this.updateDeckLayout();
            this.restartAutoplay();
            requestAnimationFrame(() => {
                leavingCard.removeAttribute('no-transition');
                this._deckAnimating = false;
            });
        };
        leavingCard.addEventListener('transitionend', onTransitionEnd);
    }
    /**
     * Brings a card from behind the pile to the top position.
     * The card is placed off-screen instantly, then transitions in as it
     * becomes the new active card.
     *
     * @param direction - The direction the card arrives from ('next' or 'prev').
     * @private
     */
    arriveDeckCard(direction) {
        if (this._deckAnimating) {
            return;
        }
        this._deckAnimating = true;
        const arrivingCard = this.items[this.selectedIndex - 1];
        // Instantly position off-screen without transition.
        arrivingCard.setAttribute('no-transition', '');
        arrivingCard.dismissDirection = direction;
        arrivingCard.isDismissed = true;
        // Force style recalculation so the browser paints the off-screen position.
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        arrivingCard.offsetHeight;
        // Re-enable transitions, then move to active position.
        arrivingCard.removeAttribute('no-transition');
        super.selectPrevious();
        this.updateDeckLayout();
        this.restartAutoplay();
        const onTransitionEnd = () => {
            arrivingCard.removeEventListener('transitionend', onTransitionEnd);
            this._deckAnimating = false;
        };
        arrivingCard.addEventListener('transitionend', onTransitionEnd);
    }
    /**
     * Scrolls the slide-mode track so that the active item is in view.
     *
     * @private
     */
    scrollToActiveItem() {
        if (!this.isOutOfView(this.selectedItem?.index)) {
            return;
        }
        let targetItemIndex = this.selectedIndex;
        // Only shift the carousel by one if we're going forwards.
        // This looks better compared to moving the carousel by an entire page.
        if (this.selectedIndex > 0 && !this.isOutOfView(this.selectedIndex - 1)) {
            targetItemIndex = this.items.findIndex((_, i) => !this.isOutOfView(i)) + 1;
        }
        this._position = this.items[targetItemIndex].offsetLeft;
        if (this._listElement) {
            this._listElement.style.transform = `translateX(-${this._position}px)`;
        }
        this.hasPrevious = this._loop ? true : this.selectedIndex > 0;
        this.hasNext = this._loop ? this._loop : false;
        for (let i = this.items.length - 1; i > -1; i--) {
            if (this.isOutOfView(i, 'next')) {
                this.hasNext = this._loop ? true : true;
                break;
            }
        }
    }
    /**
     * Checks whether an item at a specific index is outside of the viewport.
     *
     * @private
     */
    isOutOfView(index, direction) {
        const { offsetWidth, offsetLeft } = this.items[index];
        return (!direction || direction === 'prev') && offsetLeft - this._position < 0
            ? true
            : (!direction || direction === 'next') && offsetWidth + offsetLeft - this._position > (this._listElement?.clientWidth ?? 0);
    }
    /**
     * Checks whether an index is out of range.
     *
     * @private
     */
    isOutOfRange(index, direction) {
        return (direction === 'prev') && index <= 0
            ? true
            : (direction === 'next') && index >= (this.items.length - 1);
    }
    /**
     * Resolves the effective timeout for the active item.
     * Uses item-level timeout override when available.
     *
     * @private
     */
    getEffectiveTimeout() {
        return (this.selectedItem?.timeout ?? this.timeout) || 0;
    }
    /**
     * Restarts autoplay based on the current effective timeout.
     *
     * @private
     */
    restartAutoplay() {
        if (!this._autoplay) {
            return;
        }
        this._autoplayController.updateDurationAndRestart(this.getEffectiveTimeout());
    }
};
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CarouselElement.prototype, "loop", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CarouselElement.prototype, "showNavigation", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CarouselElement.prototype, "showIndicator", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CarouselElement.prototype, "hasPrevious", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CarouselElement.prototype, "hasNext", null);
__decorate([
    Attribute({ type: CarouselNavigationPosition }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CarouselElement.prototype, "navigationPosition", null);
__decorate([
    Attribute({ type: CarouselDisplayMode }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CarouselElement.prototype, "displayMode", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CarouselElement.prototype, "autoplay", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], CarouselElement.prototype, "timeout", null);
__decorate([
    Watch('autoplay'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], CarouselElement.prototype, "onAutoplayChanged", null);
__decorate([
    Watch('timeout'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], CarouselElement.prototype, "onTimeoutChanged", null);
__decorate([
    Watch('displayMode'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], CarouselElement.prototype, "onDisplayModeChanged", null);
CarouselElement = __decorate([
    Component({
        selector: 'mosaik-carousel',
        template: carouselElementTemplate,
        themes: {
            joy: carouselElementJoyStyle,
            memphis: carouselElementMemphisStyle,
            cosmopolitan: carouselElementCosmopolitanStyle
        },
        host: {
            tabIndex: '0'
        },
        imports: [
            FloatingActionButtonElement,
            CarouselItemElement
        ]
    }),
    __metadata("design:paramtypes", [])
], CarouselElement);
export { CarouselElement };
//# sourceMappingURL=CarouselElement.js.map