/**
 * ============================================
 * RSL Sticky Elements Component
 * Version: 2.0.0
 * Description: Premium sticky positioning system
 * with visual state indicators, accessibility
 * features, and responsive breakpoints.
 *
 * BACKWARD COMPATIBLE: Works with V1 classes
 * (.sticky-element, .sticky-top, etc.) while
 * adding V2 data attribute enhancements.
 * ============================================
 */

(function() {
    'use strict';

    // Ensure RSL namespace exists
    window.RSL = window.RSL || {};

    /**
     * Sticky Elements Component
     * Uses IntersectionObserver for performance-based stuck detection
     */
    const StickyElements = {
        // Track initialized instances
        _instances: new Map(),

        // Track observers
        _observers: new Map(),

        // Track scroll direction
        _lastScrollY: 0,
        _scrollDirection: 'down',

        // Track stacking groups
        _stackGroups: new Map(),

        // Track FilterBus subscriptions for cleanup
        _filterSubscriptions: new Map(),

        /**
         * Initialize all sticky elements on the page
         * @param {Object} options - Global options
         */
        init: function(options = {}) {
            const defaults = {
                rootMargin: '0px',
                threshold: [0, 1],
                announceToScreenReader: true
            };

            this._options = Object.assign({}, defaults, options);

            // Find all V2 sticky elements
            const elements = document.querySelectorAll('[data-rsl-sticky]');

            elements.forEach(el => {
                if (!this._instances.has(el)) {
                    this.create(el);
                }
            });

            // Set up scroll direction tracking
            this._setupScrollTracking();

            // Set up resize handler for responsive breakpoints
            this._setupResizeHandler();

            // Process stacking groups
            this._processStackGroups();

            return this;
        },

        /**
         * Create a sticky element instance
         * @param {HTMLElement} element - The element to make sticky
         * @param {Object} options - Instance-specific options
         */
        create: function(element, options = {}) {
            if (!element || this._instances.has(element)) {
                return this;
            }

            // Parse configuration from data attributes
            const config = this._parseConfig(element, options);

            // Store instance
            this._instances.set(element, config);

            // Add sentinel element for stuck detection
            this._addSentinel(element, config);

            // Set up IntersectionObserver
            this._observeElement(element, config);

            // Add skip link if header variant
            if (config.variant === 'header' && config.addSkipLink) {
                this._addSkipLink(element);
            }

            // Set up direction-based visibility
            if (config.direction) {
                this._setupDirectionVisibility(element, config);
            }

            // Add to stack group if applicable
            if (config.stackGroup) {
                this._addToStackGroup(element, config);
            }

            // Setup FilterBus subscription if configured
            this._setupFilterBusSubscription(element, config);

            // Add initialized class after brief delay (prevents transition flicker)
            setTimeout(function() {
                element.classList.add('rsl-sticky-initialized');
            }, 50);

            // Set initial ARIA state
            element.setAttribute('aria-live', 'off');

            return this;
        },

        /**
         * Destroy a sticky element instance
         * @param {HTMLElement} element - The element to clean up
         */
        destroy: function(element) {
            if (!element || !this._instances.has(element)) {
                return this;
            }

            const config = this._instances.get(element);

            // Unsubscribe from FilterBus
            const subscriberId = this._filterSubscriptions.get(element);
            if (subscriberId && window.RSL && window.RSL.FilterBus && window.RSL.FilterBus.unsubscribe) {
                window.RSL.FilterBus.unsubscribe(subscriberId);
            }
            this._filterSubscriptions.delete(element);

            // Remove observer
            if (this._observers.has(element)) {
                this._observers.get(element).disconnect();
                this._observers.delete(element);
            }

            // Remove sentinel
            if (config.sentinel && config.sentinel.parentNode) {
                config.sentinel.parentNode.removeChild(config.sentinel);
            }

            // Remove skip link
            const skipLink = element.querySelector('.rsl-sticky-skip-link');
            if (skipLink) {
                skipLink.parentNode.removeChild(skipLink);
            }

            // Remove from stack group
            if (config.stackGroup && this._stackGroups.has(config.stackGroup)) {
                const group = this._stackGroups.get(config.stackGroup);
                const index = group.indexOf(element);
                if (index > -1) {
                    group.splice(index, 1);
                }
            }

            // Remove stuck class and initialized class
            element.classList.remove('rsl-sticky-stuck', 'rsl-sticky-hidden', 'rsl-sticky-show-up', 'rsl-sticky-initialized', 'rsl-sticky-filter-hidden');

            // Remove instance
            this._instances.delete(element);

            return this;
        },

        /**
         * Check if element is currently stuck
         * @param {HTMLElement} element - The element to check
         * @returns {boolean}
         */
        isStuck: function(element) {
            return element && element.classList.contains('rsl-sticky-stuck');
        },

        /**
         * Get the current state of a sticky element
         * @param {HTMLElement} element - The element to check
         * @returns {Object|null}
         */
        getState: function(element) {
            if (!element || !this._instances.has(element)) {
                return null;
            }

            const config = this._instances.get(element);

            return {
                isStuck: this.isStuck(element),
                isHidden: element.classList.contains('rsl-sticky-hidden'),
                isEnabled: !element.classList.contains('rsl-sticky-disabled'),
                offset: config.offset,
                variant: config.variant,
                stackGroup: config.stackGroup,
                scrollDirection: this._scrollDirection
            };
        },

        /**
         * Enable sticky behavior
         * @param {HTMLElement} element - The element to enable
         */
        enable: function(element) {
            if (!element) return this;

            element.classList.remove('rsl-sticky-disabled');
            element.classList.add('rsl-sticky-forced');

            // Re-observe if instance exists
            if (this._instances.has(element)) {
                const config = this._instances.get(element);
                this._observeElement(element, config);
            }

            return this;
        },

        /**
         * Disable sticky behavior
         * @param {HTMLElement} element - The element to disable
         */
        disable: function(element) {
            if (!element) return this;

            element.classList.add('rsl-sticky-disabled');
            element.classList.remove('rsl-sticky-forced', 'rsl-sticky-stuck');

            // Disconnect observer
            if (this._observers.has(element)) {
                this._observers.get(element).disconnect();
            }

            return this;
        },

        /**
         * Refresh sticky element (recalculate positions)
         * @param {HTMLElement} element - The element to refresh (or all if not provided)
         */
        refresh: function(element) {
            if (element && this._instances.has(element)) {
                const config = this._instances.get(element);

                // Update sentinel position
                if (config.sentinel) {
                    const offset = this._getOffset(element, config);
                    config.sentinel.style.top = `calc(${offset} * -1 - 1px)`;
                }

                // Re-observe
                this._observeElement(element, config);
            } else {
                // Refresh all
                this._instances.forEach((config, el) => {
                    this.refresh(el);
                });

                // Recalculate stack groups
                this._processStackGroups();
            }

            return this;
        },

        /**
         * Setup FilterBus subscription for a sticky element
         * @param {HTMLElement} element - Sticky element
         * @param {Object} config - Configuration object
         * @private
         */
        _setupFilterBusSubscription: function(element, config) {
            // Check if FilterBus is available and we have a key to subscribe to
            if (!window.RSL || !window.RSL.FilterBus || !config.filterSubscribe) {
                return;
            }

            const self = this;

            // Generate unique subscriber ID
            const subscriberId = 'sticky-' + (element.id || Math.random().toString(36).substr(2, 9));

            // Subscribe to FilterBus
            window.RSL.FilterBus.subscribe(
                subscriberId,
                function(state, meta) {
                    self._handleFilterBusChange(element, config, state, meta);
                },
                { keys: [config.filterSubscribe] }
            );

            // Store subscription ID for cleanup
            this._filterSubscriptions.set(element, subscriberId);

            // Apply any existing FilterBus state on init
            const currentState = window.RSL.FilterBus.getState();
            if (Object.keys(currentState).length > 0) {
                this._handleFilterBusChange(element, config, currentState, { source: 'init' });
            }
        },

        /**
         * Handle FilterBus state changes
         * @param {HTMLElement} element - Sticky element
         * @param {Object} config - Configuration object
         * @param {Object} state - Current filter state
         * @param {Object} meta - Change metadata
         * @private
         */
        _handleFilterBusChange: function(element, config, state, meta) {
            if (!state || !config.filterSubscribe) return;

            const filterKey = config.filterSubscribe;
            const filterValue = state[filterKey];
            const componentValue = config.filterValue;

            // Determine visibility
            let shouldShow = true;

            if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
                // Check if filter is "All" (case-insensitive) - show everything
                const filterLower = String(filterValue).toLowerCase();
                if (filterLower === 'all') {
                    shouldShow = true;
                } else if (componentValue !== undefined && componentValue !== null) {
                    // Check if component's filter value matches the current filter
                    // Support comma-separated values for multi-match
                    const componentValues = String(componentValue).split(',').map(function(v) {
                        return v.trim().toLowerCase();
                    });
                    shouldShow = componentValues.includes(filterLower);
                }
                // If component has no filterValue, it always shows
            }

            // Apply visibility
            if (shouldShow) {
                element.classList.remove('rsl-sticky-filter-hidden');
                element.removeAttribute('aria-hidden');
            } else {
                element.classList.add('rsl-sticky-filter-hidden');
                element.setAttribute('aria-hidden', 'true');
            }

            // Dispatch event
            element.dispatchEvent(new CustomEvent('rsl-sticky:filterbus-update', {
                bubbles: true,
                detail: { state: state, meta: meta, visible: shouldShow }
            }));
        },

        /**
         * Publish to FilterBus when stuck state changes
         * @param {HTMLElement} element - The element
         * @param {boolean} isStuck - Whether element is stuck
         */
        _publishStuckState: function(element, isStuck) {
            const config = this._instances.get(element);

            if (config && config.filterKey && window.RSL && window.RSL.Filter) {
                window.RSL.Filter.publish(config.filterKey, {
                    element: element,
                    isStuck: isStuck,
                    variant: config.variant,
                    timestamp: Date.now()
                });
            }

            // Dispatch custom event
            const event = new CustomEvent('rsl-sticky-change', {
                bubbles: true,
                detail: {
                    element: element,
                    isStuck: isStuck,
                    state: this.getState(element)
                }
            });
            element.dispatchEvent(event);
        },

        /**
         * Parse configuration from data attributes
         * @private
         */
        _parseConfig: function(element, options) {
            return {
                offset: element.getAttribute('data-sticky-offset') || options.offset || 'md',
                shadow: element.getAttribute('data-sticky-shadow') || options.shadow || 'default',
                variant: element.getAttribute('data-sticky-variant') || options.variant || null,
                direction: element.getAttribute('data-sticky-direction') || options.direction || null,
                breakpoint: element.getAttribute('data-sticky-breakpoint') || options.breakpoint || null,
                bgStuck: element.getAttribute('data-sticky-bg-stuck') === 'true' || options.bgStuck || false,
                border: element.getAttribute('data-sticky-border') === 'true' || options.border || false,
                scale: element.getAttribute('data-sticky-scale') === 'true' || options.scale || false,
                stackGroup: element.getAttribute('data-sticky-group') || options.stackGroup || null,
                stackOrder: parseInt(element.getAttribute('data-sticky-stack') || options.stackOrder || 0, 10),
                zIndex: element.getAttribute('data-sticky-z') || options.zIndex || 'base',
                addSkipLink: element.getAttribute('data-sticky-skip-link') !== 'false',
                filterKey: element.getAttribute('data-filterbus-publish') || options.filterKey || null,
                boundary: element.getAttribute('data-sticky-boundary') || options.boundary || null,
                // FilterBus subscribe attributes
                filterSubscribe: element.getAttribute('data-filter-subscribe') || options.filterSubscribe || null,
                filterValue: element.getAttribute('data-filter-value') || options.filterValue || null,
                label: element.getAttribute('data-sticky-label') || element.getAttribute('aria-label') || options.label || null,
                sentinel: null
            };
        },

        /**
         * Get the offset value in pixels
         * @private
         */
        _getOffset: function(element, config) {
            const offsetAttr = config.offset;

            // Check for custom CSS variable
            const customOffset = element.style.getPropertyValue('--sticky-offset');
            if (customOffset) {
                return customOffset;
            }

            // Map named offsets to CSS variables
            const offsetMap = {
                '0': '0',
                'none': '0',
                'xs': 'var(--rsl-sticky-offset-xs, 0)',
                'sm': 'var(--rsl-sticky-offset-sm, 0.5rem)',
                'md': 'var(--rsl-sticky-offset-md, 1rem)',
                'lg': 'var(--rsl-sticky-offset-lg, 1.5rem)',
                'xl': 'var(--rsl-sticky-offset-xl, 2rem)'
            };

            return offsetMap[offsetAttr] || offsetAttr;
        },

        /**
         * Add sentinel element for IntersectionObserver
         * @private
         */
        _addSentinel: function(element, config) {
            // Ensure element has position relative for sentinel positioning
            const computedStyle = window.getComputedStyle(element);
            if (computedStyle.position === 'static') {
                element.style.position = 'relative';
            }

            const sentinel = document.createElement('div');
            sentinel.className = 'rsl-sticky-sentinel';
            sentinel.setAttribute('aria-hidden', 'true');

            // Position sentinel just above the sticky threshold
            const offset = this._getOffset(element, config);
            sentinel.style.top = `calc(${offset} * -1 - 1px)`;

            element.insertBefore(sentinel, element.firstChild);
            config.sentinel = sentinel;
        },

        /**
         * Set up IntersectionObserver for stuck detection
         * @private
         */
        _observeElement: function(element, config) {
            // Disconnect existing observer
            if (this._observers.has(element)) {
                this._observers.get(element).disconnect();
            }

            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    const isStuck = !entry.isIntersecting;
                    const wasStuck = element.classList.contains('rsl-sticky-stuck');

                    if (isStuck !== wasStuck) {
                        element.classList.toggle('rsl-sticky-stuck', isStuck);

                        // Announce to screen readers if enabled
                        if (this._options.announceToScreenReader) {
                            this._announceState(element, isStuck);
                        }

                        // Publish to FilterBus
                        this._publishStuckState(element, isStuck);
                    }
                });
            }, {
                rootMargin: this._options.rootMargin,
                threshold: this._options.threshold
            });

            observer.observe(config.sentinel);
            this._observers.set(element, observer);
        },

        /**
         * Announce stuck state to screen readers
         * @private
         */
        _announceState: function(element, isStuck) {
            // Check for prefers-reduced-motion
            const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

            // Find or create live region
            let liveRegion = document.getElementById('rsl-sticky-announcer');
            if (!liveRegion) {
                liveRegion = document.createElement('div');
                liveRegion.id = 'rsl-sticky-announcer';
                liveRegion.className = 'rsl-sticky-sr-only';
                liveRegion.setAttribute('aria-live', 'polite');
                liveRegion.setAttribute('aria-atomic', 'true');
                document.body.appendChild(liveRegion);
            }

            // Get element identifier
            const label = element.getAttribute('aria-label') ||
                          element.getAttribute('data-sticky-label') ||
                          element.querySelector('h1, h2, h3')?.textContent ||
                          'Navigation';

            // Announce change
            const message = isStuck
                ? `${label} is now fixed to the top of the page`
                : `${label} is no longer fixed`;

            // Only announce if not preferring reduced motion or if it's a significant change
            if (!prefersReducedMotion || element.getAttribute('data-sticky-variant') === 'header') {
                liveRegion.textContent = message;

                // Clear after announcement
                setTimeout(() => {
                    liveRegion.textContent = '';
                }, 1000);
            }
        },

        /**
         * Add skip link for keyboard navigation
         * @private
         */
        _addSkipLink: function(element) {
            // Check if skip link already exists
            if (element.querySelector('.rsl-sticky-skip-link')) {
                return;
            }

            // Find main content target
            const mainContent = document.querySelector('main, [role="main"], #main-content, .main-content');
            const targetId = mainContent?.id || 'main-content';

            // Ensure target has ID
            if (mainContent && !mainContent.id) {
                mainContent.id = targetId;
            }

            const skipLink = document.createElement('a');
            skipLink.className = 'rsl-sticky-skip-link';
            skipLink.href = `#${targetId}`;
            skipLink.textContent = 'Skip to main content';

            skipLink.addEventListener('click', (e) => {
                e.preventDefault();
                const target = document.getElementById(targetId);
                if (target) {
                    target.setAttribute('tabindex', '-1');
                    target.focus();
                    target.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }
            });

            element.insertBefore(skipLink, element.firstChild);
        },

        /**
         * Set up scroll direction tracking
         * @private
         */
        _setupScrollTracking: function() {
            if (this._scrollTrackingSetup) return;

            this._scrollTrackingSetup = true;
            this._lastScrollY = window.scrollY;

            let ticking = false;

            window.addEventListener('scroll', () => {
                if (!ticking) {
                    window.requestAnimationFrame(() => {
                        const currentScrollY = window.scrollY;
                        this._scrollDirection = currentScrollY > this._lastScrollY ? 'down' : 'up';
                        this._lastScrollY = currentScrollY;

                        // Update direction-based visibility
                        this._updateDirectionVisibility();

                        ticking = false;
                    });
                    ticking = true;
                }
            }, { passive: true });
        },

        /**
         * Set up direction-based visibility for an element
         * @private
         */
        _setupDirectionVisibility: function(element, config) {
            // Initially hidden if direction is 'up' (show on scroll up)
            if (config.direction === 'up') {
                element.classList.add('rsl-sticky-hidden');
            }
        },

        /**
         * Update visibility based on scroll direction
         * @private
         */
        _updateDirectionVisibility: function() {
            this._instances.forEach((config, element) => {
                if (config.direction === 'up') {
                    if (this._scrollDirection === 'up') {
                        element.classList.remove('rsl-sticky-hidden');
                        element.classList.add('rsl-sticky-show-up');
                    } else {
                        element.classList.add('rsl-sticky-hidden');
                        element.classList.remove('rsl-sticky-show-up');
                    }
                }
            });
        },

        /**
         * Set up resize handler for responsive breakpoints
         * @private
         */
        _setupResizeHandler: function() {
            if (this._resizeHandlerSetup) return;

            this._resizeHandlerSetup = true;

            let resizeTimeout;
            window.addEventListener('resize', () => {
                clearTimeout(resizeTimeout);
                resizeTimeout = setTimeout(() => {
                    this.refresh();
                }, 150);
            }, { passive: true });
        },

        /**
         * Add element to a stack group
         * @private
         */
        _addToStackGroup: function(element, config) {
            const groupName = config.stackGroup;

            if (!this._stackGroups.has(groupName)) {
                this._stackGroups.set(groupName, []);
            }

            const group = this._stackGroups.get(groupName);

            // Insert in order by stackOrder
            const insertIndex = group.findIndex(el => {
                const elConfig = this._instances.get(el);
                return elConfig && elConfig.stackOrder > config.stackOrder;
            });

            if (insertIndex === -1) {
                group.push(element);
            } else {
                group.splice(insertIndex, 0, element);
            }
        },

        /**
         * Process stack groups and calculate offsets
         * @private
         */
        _processStackGroups: function() {
            this._stackGroups.forEach((elements, groupName) => {
                let cumulativeOffset = 0;

                elements.forEach((element, index) => {
                    // Set stack offset CSS variable
                    element.style.setProperty('--sticky-stack-offset', `${cumulativeOffset}px`);

                    // Add accumulated height of this element for next one
                    cumulativeOffset += element.offsetHeight;
                });
            });
        }
    };

    // Expose to RSL namespace
    window.RSL.Sticky = StickyElements;

    // Auto-initialize on DOMContentLoaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            window.RSL.Sticky.init();
        });
    } else {
        window.RSL.Sticky.init();
    }

})();
