// #region Imports
import { ControllerBase } from './Abstracts/ControllerBase';
/**
 * The `HideOnScrollController` class is a reactive controller that handles scroll-related functionality.
 * This controller sets the following attributes on the host element:
 * - `data-shadow`: Indicates if the host element has a shadow.
 * - `data-visible`: Indicates if the host element is visible.
 * - `data-fixed`: Indicates if the host element is fixed.
 * - `data-animated`: Indicates if the host element is animated.
 *
 * @remarks
 * Keep in mind that this controller only sets the attributes and not the styles.
 * If you want to style the host element based on these attributes, you need to do it manually.
 *
 * @public
 */
export class HideOnScrollController extends ControllerBase {
    // #region Fields
    static SHADOW_ATTR = 'data-shadow';
    static VISIBLE_ATTR = 'data-visible';
    static FIXED_ATTR = 'data-fixed';
    static ANIMATED_ATTR = 'data-animated';
    _hideOnScroll;
    _scrollTargetFn;
    _scrollUpThreshold;
    _scrollDownThreshold;
    _behavior;
    _scrollElement;
    _scrollEventsController;
    _scrollFunction;
    _lastScroll;
    _headerOnTop;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `HideOnScrollController` class.
     *
     * @public
     */
    constructor(host, config) {
        super(host);
        this._hideOnScroll = config.hideOnScroll;
        this._scrollTargetFn = config.scrollTargetFn ?? null; // Default to null if not provided
        this._scrollUpThreshold = config.scrollUpThreshold ?? host.offsetHeight; // Default to host offsetHeight if not provided
        this._scrollDownThreshold = config.scrollDownThreshold ?? 1; // Default to 1 if not provided
        this._behavior = config.behavior ?? 'auto'; // Default to 'auto' if not provided
        this._scrollEventsController = null;
        this._headerOnTop = true;
        this._lastScroll = 0;
    }
    // #endregion
    // #region Properties
    /**
     * Gets or sets the `hideOnScroll` property.
     *
     * @public
     */
    get hideOnScroll() {
        return this._hideOnScroll;
    }
    set hideOnScroll(value) {
        if (this._hideOnScroll !== value) {
            this._hideOnScroll = value;
            this._scrollFunction = this.getScrollFunction();
        }
    }
    /**
     * Gets or sets the `scrollTargetFn` property.
     *
     * @public
     */
    get scrollTargetFn() {
        return this._scrollTargetFn;
    }
    set scrollTargetFn(value) {
        if (this._scrollTargetFn !== value) {
            this._scrollTargetFn = value;
            this.setScrollListener(this._scrollTargetFn ? this._scrollTargetFn() ?? document : document);
        }
    }
    /**
     * Gets or sets the `scrollUpThreshold` property.
     *
     * @public
     */
    get scrollUpThreshold() {
        return this._scrollUpThreshold;
    }
    set scrollUpThreshold(value) {
        if (this._scrollUpThreshold !== value) {
            this._scrollUpThreshold = value;
        }
    }
    /**
     * Gets or sets the `scrollDownThreshold` property.
     *
     * @public
     */
    get scrollDownThreshold() {
        return this._scrollDownThreshold;
    }
    set scrollDownThreshold(value) {
        if (this._scrollDownThreshold !== value) {
            this._scrollDownThreshold = value;
        }
    }
    /**
     * Gets or sets the `behavior` property.
     *
     * @public
     */
    get behavior() {
        return this._behavior;
    }
    set behavior(value) {
        if (this._behavior !== value) {
            this._behavior = value;
        }
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    hostConnected() {
        this.observe();
        if (this._headerOnTop) {
            this.showHeader();
        }
    }
    /**
     * @public
     * @override
     */
    hostDisconnected() {
        this.unobserve();
    }
    /**
     * Restores the header to its original state.
     *
     * @public
     */
    restore() {
        this._headerOnTop = true;
        this.showHeader();
    }
    /**
     * Starts observing the scroll events.
     *
     * @public
     */
    observe() {
        this.setScrollListener(this._scrollTargetFn ? this._scrollTargetFn() ?? document : document);
    }
    /**
     * Stops observing the scroll events.
     *
     * @public
     */
    unobserve() {
        this._scrollEventsController?.abort();
        this._scrollEventsController = null;
    }
    /**
     * Sets the scroll listener on the target element.
     *
     * @private
     * @param scrollOrigin - The target element for scroll events.
     */
    setScrollListener(scrollOrigin) {
        this.unobserve(); // Ensure any existing observer is removed
        this._scrollEventsController = new AbortController();
        this._scrollElement = typeof scrollOrigin === 'string' ? document.querySelector(scrollOrigin) : scrollOrigin;
        this._scrollFunction = this.getScrollFunction();
        this._scrollElement?.addEventListener('scroll', this._scrollFunction, {
            passive: true,
            signal: this._scrollEventsController.signal
        });
    }
    /**
     * Returns the appropriate scroll function based on the hideOnScroll property.
     *
     * @private
     */
    getScrollFunction() {
        return this._hideOnScroll ? this.handleScrollWithHide.bind(this) : this.handleScrollWithShadow.bind(this);
    }
    /**
     * Handles the scroll events when hideOnScroll is true.
     *
     * @private
     */
    handleScrollWithHide() {
        const currentScroll = this.getCurrentScrollProperty('scrollTop');
        if (this.isBouncingAtEdge(currentScroll)) {
            return;
        }
        this.host.toggleAttribute(HideOnScrollController.SHADOW_ATTR, currentScroll !== 0);
        if (this.isScrollingDown(currentScroll)) {
            this.hideHeader();
        }
        else if (this.isScrollingUp(currentScroll)) {
            this.showHeader();
        }
        this._lastScroll = Math.max(0, currentScroll);
    }
    /**
     * Handles the scroll events when hideOnScroll is false.
     *
     * @private
     */
    handleScrollWithShadow() {
        this.host.toggleAttribute(HideOnScrollController.SHADOW_ATTR, this.getCurrentScrollProperty('scrollTop') !== 0);
    }
    /**
     * Returns the requested scroll property of the scroll context.
     *
     * @private
     * @param property - The scroll property to retrieve.
     */
    getCurrentScrollProperty(property) {
        return this._scrollElement instanceof Document
            ? this._scrollElement.documentElement[property] || this._scrollElement.body[property]
            : this._scrollElement?.[property] ?? 0;
    }
    /**
     * Checks if the scroll view is bouncing past the edge of content.
     *
     * @private
     * @param currentScroll - The current scroll position.
     */
    isBouncingAtEdge(currentScroll) {
        return this.getCurrentScrollProperty('scrollHeight') - window.innerHeight - currentScroll <= 0;
    }
    /**
     * Checks if the user is scrolling down.
     *
     * @private
     * @param currentScroll - The current scroll position.
     */
    isScrollingDown(currentScroll) {
        const scrollDownThreshold = this._scrollDownThreshold ?? this.host.offsetHeight;
        return currentScroll > scrollDownThreshold && currentScroll > this._lastScroll;
    }
    /**
     * Checks if the user is scrolling up past the threshold.
     *
     * @private
     * @param currentScroll - The current scroll position.
     */
    isScrollingUp(currentScroll) {
        return this._lastScroll - currentScroll > this._scrollUpThreshold;
    }
    /**
     * Hides the header when scrolling down.
     *
     * @private
     */
    hideHeader() {
        if (this._behavior === 'auto') {
            this._headerOnTop = false;
            [
                HideOnScrollController.SHADOW_ATTR,
                HideOnScrollController.VISIBLE_ATTR
            ].forEach((attr) => {
                this.host.removeAttribute(attr);
            });
        }
        else {
            const currentScroll = this.getCurrentScrollProperty('scrollTop');
            if (currentScroll > this._scrollDownThreshold) {
                const height = this.host.offsetHeight;
                const diff = currentScroll - height;
                if (diff > 0 && diff < height) {
                    this._headerOnTop = false;
                    this.host.style.top = `-${diff}px`;
                }
            }
        }
    }
    /**
     * Shows the header when scrolling up past the threshold.
     *
     * @private
     */
    showHeader() {
        if (this._behavior === 'auto') {
            this._headerOnTop = true;
            [
                HideOnScrollController.FIXED_ATTR,
                HideOnScrollController.ANIMATED_ATTR,
                HideOnScrollController.VISIBLE_ATTR
            ].forEach((attr) => this.host.toggleAttribute(attr, true));
            [HideOnScrollController.SHADOW_ATTR].forEach((attr) => this.host.toggleAttribute(attr, this.getCurrentScrollProperty('scrollTop') !== 0));
        }
        else {
            const currentScroll = this.getCurrentScrollProperty('scrollTop');
            const scrollDiff = this._lastScroll - currentScroll;
            // const currentTop = parseInt(this.host.style.top, 10) || 0;
            this.host.style.top = `${scrollDiff}px`;
        }
    }
}
//# sourceMappingURL=HideOnScrollController.js.map