// #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 { Orientable } from '../../../Behaviors/Orientable';
import { Slottable } from '../../../Behaviors/Slottable';
import { Themeable } from '../../../Behaviors/Themeable';
import { IntersectionAnchorController } from '../../../Controllers/IntersectionAnchorController';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { Orientation } from '../../../Types/Orientation';
import { CustomElement } from '../../Abstracts/CustomElement';
import { ButtonElement } from '../../Buttons/Button/ButtonElement';
import { IScrollElementProps } from './IScrollElementProps';
import { scrollElementTemplate } from './ScrollElementTemplate';
import { scrollElementCosmopolitanStyle } from './Themes/ScrollElement.Cosmopolitan';
import { scrollElementJoyStyle } from './Themes/ScrollElement.Joy';
import { scrollElementMemphisStyle } from './Themes/ScrollElement.Memphis';
// #endregion
/**
 * Scroll - A scrollable container with scroll position detection and optional navigation buttons.
 *
 * @description
 * The Scroll component provides a scrollable container that automatically detects when the user
 * has scrolled to the top, bottom, start, or end of the content. It supports both vertical
 * (default) and horizontal scrolling with optional navigation buttons for horizontal scroll.
 * When `showScrollButtons` is true, a CSS grid layout with prev/next arrow buttons and
 * gradient mask indicators is applied, following the Porsche Design System scroller pattern.
 * The scroll area spans the entire grid while action buttons overlay on top. The component
 * uses Intersection Observer API via invisible trigger elements to efficiently track scroll
 * boundaries without performance overhead.
 *
 * @name Scroll
 * @element mosaik-scroll
 * @category Primitives
 *
 * @slot - The default content slot for scrollable content
 *
 * @csspart actionNext - The wrapper for the next scroll button (overlays the scroll area)
 * @csspart actionPrev - The wrapper for the previous scroll button (overlays the scroll area)
 * @csspart anchorBottom - The bottom anchor element for vertical scroll detection
 * @csspart anchorTop - The top anchor element for vertical scroll detection
 * @csspart content - The inner scroll wrapper (inline-flex, holds slot and triggers)
 * @csspart root - The outer grid container element
 * @csspart scrollButtonNext - The next (right/down) navigation button
 * @csspart scrollButtonPrev - The previous (left/up) navigation button
 * @csspart scroller - The scrollable area element (spans the full grid)
 * @csspart triggerEnd - The triggerEnd part.
 * @csspart triggerStart - The triggerStart part.
 *
 * @cssprop {String} --scroll-action-button-width - The width of the action button wrapper area
 * @cssprop {String} --scroll-font-family - The font family CSS custom property.
 * @cssprop {String} --scroll-font-letter-spacing - The font letter spacing CSS custom property.
 * @cssprop {String} --scroll-font-line-height - The font line height CSS custom property.
 * @cssprop {String} --scroll-font-size - The font size CSS custom property.
 * @cssprop {String} --scroll-font-text-decoration - The font text decoration CSS custom property.
 * @cssprop {String} --scroll-font-text-transform - The font text transform CSS custom property.
 * @cssprop {String} --scroll-font-weight - The font weight CSS custom property.
 * @cssprop {String} --scroll-gap - The gap CSS custom property.
 * @cssprop {String} --scroll-gradient-size - The size of the gradient fade indicator at scroll edges
 * @cssprop {String} --scroll-padding-bottom - The padding bottom CSS custom property.
 * @cssprop {String} --scroll-padding-left - The padding left CSS custom property.
 * @cssprop {String} --scroll-padding-right - The padding right CSS custom property.
 * @cssprop {String} --scroll-padding-top - The padding top CSS custom property.
 * @cssprop {String} --scroll-shadow - The shadow CSS custom property.
 * @cssprop {String} --scroll-shadow-blur - The shadow blur CSS custom property.
 * @cssprop {String} --scroll-shadow-color - The shadow color CSS custom property.
 * @cssprop {String} --scroll-shadow-offset-x - The shadow offset x CSS custom property.
 * @cssprop {String} --scroll-shadow-offset-y - The shadow offset y CSS custom property.
 * @cssprop {String} --scroll-shadow-spread - The shadow spread CSS custom property.
 * @cssprop {String} --scroll-transition-duration - The duration of the opacity transition for action buttons
 * @cssprop {String} --scroll-transition-mode - The easing function of the opacity transition for action buttons
 * @cssprop {String} --scroll-transition-property - The CSS properties to transition (e.g. background-color, box-shadow, transform)
 * @cssprop {String} --scroll-translate - The translate CSS custom property.
 *
 * @fires connected {ConnectedEvent} - Fired when connected to the DOM
 * @fires changed {PropertyChangedEvent} - Fired when properties change
 *
 * @dependency mosaik-button - The Button element.
 *
 * @example
 * Basic scrollable container:
 * ```html
 * <mosaik-scroll>
 *   <div>Long content here...</div>
 * </mosaik-scroll>
 * ```
 *
 * @example
 * Horizontal scroller with navigation buttons:
 * ```html
 * <mosaik-scroll show-scroll-buttons style="white-space: nowrap;">
 *   <mosaik-button label="Item 1"></mosaik-button>
 *   <mosaik-button label="Item 2"></mosaik-button>
 *   <mosaik-button label="Item 3"></mosaik-button>
 *   <!-- many more items -->
 * </mosaik-scroll>
 * ```
 *
 * @example
 * Detecting scroll position:
 * ```html
 * <mosaik-scroll id="myScroll">
 *   <div>Content...</div>
 * </mosaik-scroll>
 * <script>
 *   const scroll = document.querySelector('#myScroll');
 *   scroll.addEventListener('changed', (e) => {
 *     if (e.detail.propertyName === 'isAtScrollBottom' && e.detail.value) {
 *       console.log('Scrolled to bottom!');
 *     }
 *   });
 * </script>
 * ```
 *
 * @public
 */
let ScrollElement = class ScrollElement extends Themeable(Orientable(Slottable(CustomElement))) {
    // #region Fields
    _intersectionAnchorController;
    _scrollIntersectionObserver;
    _isAtScrollTop;
    _isAtScrollBottom;
    _isAtScrollStart;
    _isAtScrollEnd;
    _showScrollButtons;
    _showScrollbar;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._isAtScrollTop = false;
        this._isAtScrollBottom = false;
        this._isAtScrollStart = true;
        this._isAtScrollEnd = true;
        this._showScrollButtons = IScrollElementProps.DEFAULTS.showScrollButtons;
        this._showScrollbar = IScrollElementProps.DEFAULTS.showScrollbar;
        this._scrollIntersectionObserver = null;
        this._intersectionAnchorController = new IntersectionAnchorController(this, {
            target: '[part="scroller"]',
            anchor: {
                top: '[part="anchorTop"]',
                bottom: '[part="anchorBottom"]'
            },
            callback: (anchor) => {
                this.isAtScrollTop = anchor.isAtScrollTop;
                this.isAtScrollBottom = anchor.isAtScrollBottom;
            }
        });
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     * The `is` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-scroll';
    }
    /**
     * Gets or sets the `isAtScrollTop` property.
     *
     * @public
     * @readonly
     * @attr
     */
    get isAtScrollTop() {
        return this._isAtScrollTop;
    }
    set isAtScrollTop(value) {
        if (this._isAtScrollTop !== value) {
            this._isAtScrollTop = value;
            this.requestUpdate('isAtScrollTop');
        }
    }
    /**
     * Gets or sets the `isAtScrollBottom` property.
     *
     * @public
     * @readonly
     * @attr
     */
    get isAtScrollBottom() {
        return this._isAtScrollBottom;
    }
    set isAtScrollBottom(value) {
        if (this._isAtScrollBottom !== value) {
            this._isAtScrollBottom = value;
            this.requestUpdate('isAtScrollBottom');
        }
    }
    /**
     * Gets the `isAtScrollStart` property.
     * Indicates whether the scroll area is at the horizontal start (left in LTR).
     *
     * @public
     * @readonly
     * @attr
     */
    get isAtScrollStart() {
        return this._isAtScrollStart;
    }
    set isAtScrollStart(value) {
        if (this._isAtScrollStart !== value) {
            this._isAtScrollStart = value;
            this.requestUpdate('isAtScrollStart');
        }
    }
    /**
     * Gets the `isAtScrollEnd` property.
     * Indicates whether the scroll area is at the horizontal end (right in LTR).
     *
     * @public
     * @readonly
     * @attr
     */
    get isAtScrollEnd() {
        return this._isAtScrollEnd;
    }
    set isAtScrollEnd(value) {
        if (this._isAtScrollEnd !== value) {
            this._isAtScrollEnd = value;
            this.requestUpdate('isAtScrollEnd');
        }
    }
    /**
     * Gets or sets the `showScrollButtons` property.
     * When `true`, prev/next navigation buttons are shown overlaying the scroll area.
     * The buttons are positioned at the edges and hidden when the scroll position is
     * already at the respective boundary. This property is independent of gradient
     * indicators and scrollbar visibility.
     * When `false`, no navigation buttons are shown.
     * The default value is `false`.
     *
     * @public
     * @attr
     */
    get showScrollButtons() {
        return this._showScrollButtons;
    }
    set showScrollButtons(value) {
        if (this._showScrollButtons !== value) {
            this._showScrollButtons = value;
            this.requestUpdate('showScrollButtons');
        }
    }
    /**
     * Gets or sets the `scrollbar` property.
     * When `true`, the native scrollbar is visible.
     * When `false`, the scrollbar is hidden.
     * The default value is `false`.
     *
     * @public
     * @attr
     */
    get showScrollbar() {
        return this._showScrollbar;
    }
    set showScrollbar(value) {
        if (this._showScrollbar !== value) {
            this._showScrollbar = value;
            this.requestUpdate('showScrollbar');
        }
    }
    /**
     * Returns whether horizontal scrolling is possible.
     *
     * @public
     * @readonly
     */
    get isScrollable() {
        return !(this._isAtScrollStart && this._isAtScrollEnd);
    }
    // #endregion
    // #region Methods
    /**
     * @protected
     * @override
     */
    onApplyTemplate() {
        super.onApplyTemplate();
        this.initScrollIntersectionObserver();
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        this._scrollIntersectionObserver?.disconnect();
        this._scrollIntersectionObserver = null;
    }
    /**
     * Scrolls the content in the specified direction by approximately 80% of the visible dimension.
     * The scroll axis depends on the current `orientation`.
     *
     * @param direction - The scroll direction (`'prev'` or `'next'`).
     * @public
     */
    scrollByDirection(direction) {
        const scrollerElement = this.getTemplatePart('scroller');
        if (!scrollerElement) {
            return;
        }
        const isHorizontal = this.orientation === Orientation.Horizontal;
        const visibleSize = isHorizontal ? scrollerElement.clientWidth : scrollerElement.clientHeight;
        const scrollByAmount = visibleSize * 0.4; // Scroll by 40% of the visible area for better UX
        const currentPosition = isHorizontal ? scrollerElement.scrollLeft : scrollerElement.scrollTop;
        const targetPosition = direction === 'next'
            ? currentPosition + scrollByAmount
            : currentPosition - scrollByAmount;
        scrollerElement.scrollTo({
            left: isHorizontal ? targetPosition : undefined,
            top: isHorizontal ? undefined : targetPosition,
            behavior: 'smooth'
        });
    }
    /**
     * Scrolls the content to a specific position in pixels.
     * The scroll axis depends on the current `orientation`.
     *
     * @param position - The target scroll position in pixels.
     * @param isSmooth - Whether the scrolling should be animated (smooth) or immediate. Defaults to `true`.
     * @public
     */
    scrollToPosition(position, isSmooth = true) {
        const scrollerElement = this.getTemplatePart('scroller');
        if (!scrollerElement) {
            return;
        }
        const isHorizontal = this.orientation === Orientation.Horizontal;
        if (isSmooth) {
            scrollerElement.scrollTo({
                left: isHorizontal ? position : undefined,
                top: isHorizontal ? undefined : position,
                behavior: 'smooth'
            });
        }
        else if (isHorizontal) {
            scrollerElement.scrollLeft = position;
        }
        else {
            scrollerElement.scrollTop = position;
        }
    }
    /**
     * Called when the `orientation` property changes.
     * Re-initializes the intersection observer for the correct axis.
     *
     * @param previous - The previous value.
     * @param next - The next value.
     * @protected
     */
    onOrientationChanged(previous, next) {
        if (!this.hasUpdated) {
            return;
        }
        this.initScrollIntersectionObserver();
    }
    /**
     * Initializes the IntersectionObserver for the trigger elements inside the scroll-wrapper
     * (content part). Uses two invisible 1px triggers positioned at the start and end of the
     * content. When a trigger scrolls out of view, the gradient indicators become visible and
     * (if enabled) the corresponding scroll button appears.
     *
     * @private
     */
    initScrollIntersectionObserver() {
        this._scrollIntersectionObserver?.disconnect();
        const scrollerElement = this.getTemplatePart('scroller');
        const firstTrigger = this.getTemplatePart('triggerStart');
        const lastTrigger = this.getTemplatePart('triggerEnd');
        if (!scrollerElement || !firstTrigger || !lastTrigger) {
            return;
        }
        this._scrollIntersectionObserver = new IntersectionObserver((entries) => {
            for (const entry of entries) {
                if (entry.target === firstTrigger) {
                    this.isAtScrollStart = entry.isIntersecting;
                }
                else if (entry.target === lastTrigger) {
                    this.isAtScrollEnd = entry.isIntersecting;
                }
            }
        }, {
            root: scrollerElement,
            threshold: 0.1
        });
        this._scrollIntersectionObserver.observe(firstTrigger);
        this._scrollIntersectionObserver.observe(lastTrigger);
        // Force-reflect initial property values to DOM attributes.
        // The constructor sets _isAtScrollStart/_isAtScrollEnd directly
        // (bypassing Lit's reactive system), so when the IntersectionObserver
        // fires with the same value the setter's change guard prevents
        // requestUpdate and the attributes are never reflected. Setting
        // them here ensures the CSS :host([is-at-scroll-start]) selectors
        // match from the start; the observer will correct them asynchronously.
        this.toggleAttribute('is-at-scroll-start', this._isAtScrollStart);
        this.toggleAttribute('is-at-scroll-end', this._isAtScrollEnd);
    }
};
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ScrollElement.prototype, "isAtScrollTop", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ScrollElement.prototype, "isAtScrollBottom", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ScrollElement.prototype, "isAtScrollStart", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ScrollElement.prototype, "isAtScrollEnd", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ScrollElement.prototype, "showScrollButtons", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], ScrollElement.prototype, "showScrollbar", null);
__decorate([
    Watch('orientation', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", void 0)
], ScrollElement.prototype, "onOrientationChanged", null);
ScrollElement = __decorate([
    Component({
        selector: 'mosaik-scroll',
        template: scrollElementTemplate,
        themes: {
            joy: scrollElementJoyStyle,
            memphis: scrollElementMemphisStyle,
            cosmopolitan: scrollElementCosmopolitanStyle
        },
        imports: [
            ButtonElement
        ]
    }),
    __metadata("design:paramtypes", [])
], ScrollElement);
export { ScrollElement };
//# sourceMappingURL=ScrollElement.js.map