/**
 * RSL Card Height Equalizer
 *
 * Dynamically adjusts card heights within slot layouts to create uniform
 * row-based alignment. Ensures cards in the same row have equal heights
 * and card headers align across the row. Includes intelligent image loading
 * detection to recalculate after all images are loaded.
 *
 * @fileoverview Row-based card height equalization system with FilterBus integration
 * @version 2.3.0
 * @license MIT
 *
 * Key Features:
 * - Row-based height equalization
 * - Card header alignment within rows
 * - Automatic image load detection
 * - Responsive to layout changes
 * - Opt-in via data-same-size attribute
 * - Works with slot layout grid system
 * - JSON Layout API compatible
 * - FilterBus integration for reactive filtering
 *
 * Usage:
 * <div class="slot-layout" data-cols-md="3">
 *   <div class="card" data-same-size="true">
 *     <img class="card-img" data-img-src="image.jpg" alt="">
 *     <div class="card-header">Title</div>
 *     <div class="card-body">Content...</div>
 *   </div>
 * </div>
 *
 * FilterBus Usage:
 * <div class="card" data-filter-subscribe="category" data-filter-value="tech">
 *   <div class="card-header">Tech Card</div>
 *   <div class="card-body">This card shows when category="tech" or "all"</div>
 * </div>
 *
 * Required Elements:
 * - .slot-layout: Container for cards
 * - .card[data-same-size="true"]: Cards to equalize
 * - .card-header: Card header element (optional but recommended)
 * - .card-img[data-img-src]: Lazy-loaded images (optional)
 *
 * FilterBus Data Attributes:
 * - data-filter-subscribe: FilterBus key to listen to
 * - data-filter-value: Value(s) this card matches (comma-separated for multiple)
 *
 * How It Works:
 * 1. Groups cards by their vertical position (row detection)
 * 2. Finds the tallest card, header, and title in each row
 * 3. Sets all cards in the row to match the maximum heights
 * 4. Waits for images to load before final adjustment
 * 5. FilterBus: Shows/hides cards based on filter state changes
 *
 * Public API:
 * - RSL.Cards.init() - Initialize all card equalizers (JSON API compatible)
 * - RSL.Cards.equalize(container) - Equalize cards in specific container
 * - RSL.Cards.reset() - Reset all card heights
 * - RSL.Cards.instances - Map of initialized containers
 * - RSL.Cards.initFilterBus() - Initialize FilterBus subscriptions
 * - RSL.Cards.destroyFilterBus(card) - Destroy FilterBus subscription for a card
 * - RSL.Cards.filterSubscriptions - Map of FilterBus subscriptions
 *
 * JSON Layout API:
 * This component is fully compatible with the RSL JSON Layout API.
 * Cards rendered via JSON will automatically initialize when
 * RSL.Cards.init() is called (automatic in JSON rendering).
 *
 * Events:
 * - rsl-card:filterbus-update - Fired when FilterBus updates card visibility
 *   Detail: { state: Object, visible: Boolean }
 */

(function() {
    'use strict';

    // Initialize RSL namespace
    window.RSL = window.RSL || {};

    /**
     * Track initialized container instances to prevent double-initialization
     * @type {Map<HTMLElement, Object>}
     */
    const instances = new Map();

    /**
     * Track FilterBus subscriptions for cleanup
     * @type {Map<HTMLElement, string>}
     */
    const filterSubscriptions = new Map();

    /**
     * Track date filter subscriptions for containers
     * @type {Map<HTMLElement, string>}
     */
    const dateFilterSubscriptions = new Map();

    /**
     * Track cards with date filtering for efficient updates
     * @type {Map<HTMLElement, Array<HTMLElement>>}
     */
    const dateFilteredCards = new Map();

    /**
     * Parse date string into Date object
     * Supports ISO (YYYY-MM-DD), US (MM/DD/YYYY), and European (DD.MM.YYYY) formats
     * @param {string} dateStr - Date string to parse
     * @returns {Date|null} Parsed date or null if invalid
     */
    function parseDate(dateStr) {
        if (!dateStr) return null;

        // Already a Date object
        if (dateStr instanceof Date) return dateStr;

        var str = String(dateStr).trim();

        // ISO format: YYYY-MM-DD
        if (/^\d{4}-\d{2}-\d{2}/.test(str)) {
            var d = new Date(str);
            if (!isNaN(d.getTime())) return d;
        }

        // US format: MM/DD/YYYY
        if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(str)) {
            var parts = str.split('/');
            var d = new Date(parseInt(parts[2]), parseInt(parts[0]) - 1, parseInt(parts[1]));
            if (!isNaN(d.getTime())) return d;
        }

        // European format: DD.MM.YYYY
        if (/^\d{1,2}\.\d{1,2}\.\d{4}$/.test(str)) {
            var parts = str.split('.');
            var d = new Date(parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]));
            if (!isNaN(d.getTime())) return d;
        }

        // Fallback: try native parsing
        var fallback = new Date(str);
        return isNaN(fallback.getTime()) ? null : fallback;
    }

    /**
     * Setup FilterBus subscription for a card
     * @param {HTMLElement} card - Card element
     */
    function setupFilterBusSubscription(card) {
        var filterSubscribe = card.dataset.filterSubscribe;
        var filterValue = card.dataset.filterValue;

        // Check if FilterBus is available and we have a key to subscribe to
        if (!window.RSL || !window.RSL.FilterBus || !filterSubscribe) {
            return;
        }

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

        // Subscribe to FilterBus
        window.RSL.FilterBus.subscribe(
            subscriberId,
            function(state, meta) {
                handleFilterBusChange(card, filterSubscribe, filterValue, state);
            },
            { keys: [filterSubscribe] }
        );

        // Store subscription ID for cleanup
        filterSubscriptions.set(card, subscriberId);

        // Apply any existing FilterBus state on init
        var currentState = window.RSL.FilterBus.getState();
        if (Object.keys(currentState).length > 0) {
            handleFilterBusChange(card, filterSubscribe, filterValue, currentState);
        }

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

    /**
     * Handle FilterBus state changes for a card
     * @param {HTMLElement} card - Card element
     * @param {string} filterKey - The filter key being watched
     * @param {string} filterValue - The value this card matches
     * @param {Object} state - Current filter state
     */
    function handleFilterBusChange(card, filterKey, filterValue, state) {
        if (!state || !filterKey) return;

        var currentFilterValue = state[filterKey];

        // Determine visibility
        var shouldShow = true;

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

        // Apply visibility
        if (shouldShow) {
            card.classList.remove('card-filterbus-hidden');
            card.removeAttribute('aria-hidden');
        } else {
            card.classList.add('card-filterbus-hidden');
            card.setAttribute('aria-hidden', 'true');
        }

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

    /**
     * Initialize FilterBus subscriptions for all cards with data-filter-subscribe
     */
    function initFilterBusCards() {
        var cards = document.querySelectorAll('.card[data-filter-subscribe]');
        cards.forEach(function(card) {
            if (!filterSubscriptions.has(card)) {
                setupFilterBusSubscription(card);
            }
        });
    }

    /**
     * Destroy FilterBus subscription for a card
     * @param {HTMLElement} card - Card element
     */
    function destroyFilterBusSubscription(card) {
        var subscriberId = filterSubscriptions.get(card);
        if (subscriberId && window.RSL && window.RSL.FilterBus && window.RSL.FilterBus.unsubscribe) {
            window.RSL.FilterBus.unsubscribe(subscriberId);
        }
        filterSubscriptions.delete(card);
    }

    /**
     * Setup date filter subscription for a card container
     * @param {HTMLElement} container - Container element with data-filterbus-date-field
     */
    function setupDateFilterSubscription(container) {
        // Check if FilterBus is available
        if (!window.RSL || !window.RSL.FilterBus) {
            return;
        }

        // Check if container has date field attribute
        // data-filterbus-date-field becomes dataset.filterbusDateField in JS
        var dateFieldAttr = container.dataset.filterbusDateField;
        if (!dateFieldAttr) {
            return;
        }

        // Skip if already subscribed
        if (dateFilterSubscriptions.has(container)) {
            return;
        }

        // Find all cards in this container
        var cards = container.querySelectorAll('.card');
        if (cards.length === 0) {
            return;
        }

        // Store cards for this container
        dateFilteredCards.set(container, Array.from(cards));

        // Generate unique subscriber ID
        var subscriberId = 'cards-date-' + (container.id || Math.random().toString(36).substr(2, 9));

        // Subscribe to ALL keys to catch date objects
        window.RSL.FilterBus.subscribe(
            subscriberId,
            function(state, meta) {
                handleDateFilterChange(container, state, meta);
            }
            // No keys filter - subscribe to all
        );

        // Store subscription for cleanup
        dateFilterSubscriptions.set(container, subscriberId);
    }

    /**
     * Handle date filter changes for cards in a container
     * @param {HTMLElement} container - The container element
     * @param {Object} state - The current filter state object
     * @param {Object} meta - Change metadata
     */
    function handleDateFilterChange(container, state, meta) {
        var cards = dateFilteredCards.get(container);
        if (!cards || cards.length === 0) return;

        // Get the date field from the container
        // data-filterbus-date-field becomes dataset.filterbusDateField in JS
        var dateField = container.dataset.filterbusDateField || 'date';

        // Find date filter in state - look for objects with mode: 'range' or 'single'
        var dateFilter = null;
        Object.keys(state).forEach(function(key) {
            var value = state[key];
            if (value && typeof value === 'object' && (value.mode === 'range' || value.mode === 'single')) {
                dateFilter = value;
            }
        });

        // Apply date filter to each card
        cards.forEach(function(card) {
            // Get date from the field specified on the container
            var cardDateStr = card.dataset[dateField];

            if (!cardDateStr) {
                // No date on card, don't hide it based on date filter
                card.classList.remove('card-date-hidden');
                return;
            }

            var cardDate = parseDate(cardDateStr);
            if (!cardDate) {
                // Invalid date, don't hide
                card.classList.remove('card-date-hidden');
                return;
            }

            // If no date filter active, show the card
            if (!dateFilter || !dateFilter.startDate) {
                card.classList.remove('card-date-hidden');
                return;
            }

            // Parse filter dates
            var filterStart = parseDate(dateFilter.startDate);
            var filterEnd = parseDate(dateFilter.endDate || dateFilter.startDate);

            if (!filterStart || !filterEnd) {
                card.classList.remove('card-date-hidden');
                return;
            }

            // Make end date inclusive (end of day)
            filterEnd.setHours(23, 59, 59, 999);

            // Check if card date is within range
            var shouldShow = cardDate >= filterStart && cardDate <= filterEnd;

            if (shouldShow) {
                card.classList.remove('card-date-hidden');
            } else {
                card.classList.add('card-date-hidden');
            }
        });

        // Dispatch event
        container.dispatchEvent(new CustomEvent('rsl-cards:date-filter', {
            bubbles: true,
            detail: {
                dateFilter: dateFilter,
                totalCards: cards.length,
                visibleCards: cards.filter(function(card) {
                    return !card.classList.contains('card-date-hidden');
                }).length
            }
        }));
    }

    /**
     * Initialize date filter subscriptions for all containers with data-filterbus-date-field
     */
    function initDateFilterContainers() {
        var containers = document.querySelectorAll('.slot-layout[data-filterbus-date-field]');
        containers.forEach(function(container) {
            setupDateFilterSubscription(container);
        });
    }

    /**
     * Adjust card heights to match within rows for a specific container
     *
     * Finds all cards marked with data-same-size="true" and groups them
     * by row based on their vertical position (offsetTop). Within each row:
     * - Equalizes total card height to the tallest card
     * - Equalizes card header height to the tallest header
     * - Equalizes card title height to the tallest title
     *
     * This creates a uniform grid appearance where cards, headers, and titles
     * align horizontally across rows.
     *
     * @param {HTMLElement} container - Container element (typically .slot-layout)
     *
     * @example
     * const container = document.querySelector('.slot-layout');
     * adjustCardHeight(container);
     */
    function adjustCardHeight(container) {
        // Get cards within this specific container
        const cards = container.querySelectorAll('.card[data-same-size="true"]');

        // Reset all card heights to auto for fresh calculation
        cards.forEach(card => {
            card.style.height = 'auto';
            const header = card.querySelector('.card-header');
            if (header) {
                header.style.height = 'auto';
            }
            const title = card.querySelector('.card-title');
            if (title) {
                title.style.height = 'auto';
            }
        });

        // Array to store unique row start positions (offsetTop values)
        let rowStarts = [];

        // Iterate through all cards to group by row
        for (let i = 0; i < cards.length; i++) {
            const card = cards[i];

            // Get the card's vertical position (top offset)
            const cardRowStart = card.offsetTop;

            // If this is a new row (new offsetTop value)
            if (!rowStarts.includes(cardRowStart)) {
                // Track this new row position
                rowStarts.push(cardRowStart);

                // Get all cards that share this row position
                const cardsInRow = Array.from(cards).filter(c => c.offsetTop === cardRowStart);

                // Find the maximum card height in this row
                const maxHeight = Math.max(...cardsInRow.map(c => c.offsetHeight));

                // Find the maximum header height in this row
                const maxHeaderHeight = Math.max(...cardsInRow.map(c => {
                    const header = c.querySelector('.card-header');
                    return header ? header.offsetHeight : 0;
                }));

                // Find the maximum title height in this row
                const maxTitleHeight = Math.max(...cardsInRow.map(c => {
                    const title = c.querySelector('.card-title');
                    return title ? title.offsetHeight : 0;
                }));

                // Apply the maximum heights to all cards in this row
                cardsInRow.forEach(c => {
                    c.style.height = `${maxHeight}px`;

                    const header = c.querySelector('.card-header');
                    if (header && maxHeaderHeight > 0) {
                        header.style.height = `${maxHeaderHeight}px`;
                    }

                    const title = c.querySelector('.card-title');
                    if (title && maxTitleHeight > 0) {
                        title.style.height = `${maxTitleHeight}px`;
                    }
                });
            }
        }
    }

    /**
     * Initialize card equalization for a specific container
     *
     * Sets up image loading detection and equalizes card heights
     * after all images have loaded.
     *
     * @param {HTMLElement} container - Container element with cards
     * @returns {Object} Instance object
     *
     * @example
     * const container = document.querySelector('.slot-layout');
     * RSL.Cards.create(container);
     */
    function createCardEqualizer(container) {
        // Prevent double-initialization
        if (instances.has(container)) {
            return instances.get(container);
        }

        const images = container.querySelectorAll('.card-img');
        let imagesLoaded = 0;

        /**
         * Handle image load completion
         * Adjusts card heights when all images in this container have loaded
         */
        function onImageLoad() {
            imagesLoaded++;

            // If all images have loaded, perform final height adjustment
            if (imagesLoaded === images.length) {
                adjustCardHeight(container);
            }
        }

        /**
         * Initialize lazy-loaded images and track loading
         */
        images.forEach(function(img) {
            // Trigger image load by setting src from data attribute
            if (img.dataset.imgSrc && !img.src) {
                img.src = img.dataset.imgSrc;
            }

            // Handle image load event
            img.addEventListener('load', onImageLoad);

            // Handle images that fail to load
            img.addEventListener('error', onImageLoad);
        });

        /**
         * Fallback: If no images, adjust immediately
         */
        if (images.length === 0) {
            // Small delay to ensure DOM is fully rendered
            setTimeout(function() {
                adjustCardHeight(container);
            }, 100);
        }

        // Store instance
        const instance = {
            container: container,
            imageCount: images.length,
            imagesLoaded: imagesLoaded
        };
        instances.set(container, instance);

        return instance;
    }

    /**
     * Auto-initialize all card containers on the page
     *
     * Finds all .slot-layout containers with cards and initializes equalization.
     * Also initializes FilterBus subscriptions for cards with data-filter-subscribe.
     * Called automatically on DOM ready and by the JSON Layout API after rendering.
     *
     * @returns {void}
     *
     * @example
     * // Called automatically, or manually for dynamic content:
     * autoInit();
     */
    function autoInit() {
        // Find all slot layouts that contain cards with data-same-size
        const containers = document.querySelectorAll('.slot-layout');

        containers.forEach(function(container) {
            // Only initialize if container has cards with data-same-size
            const hasEqualizeCards = container.querySelector('.card[data-same-size="true"]');
            if (hasEqualizeCards) {
                createCardEqualizer(container);
            }
        });

        // Initialize FilterBus subscriptions for cards
        initFilterBusCards();

        // Initialize date filter subscriptions for containers
        initDateFilterContainers();
    }

    /**
     * Reset all card heights to auto
     *
     * Removes explicit height styles from all cards and headers.
     * Useful before recalculating or when layout changes.
     *
     * @returns {void}
     *
     * @example
     * RSL.Cards.reset();
     */
    function resetCardHeights() {
        const cards = document.querySelectorAll('.card[data-same-size="true"]');

        cards.forEach(function(card) {
            card.style.height = 'auto';
            const header = card.querySelector('.card-header');
            if (header) {
                header.style.height = 'auto';
            }
        });
    }

    /**
     * Public API
     *
     * @namespace RSL.Cards
     * @property {Function} init - Initialize all card equalizers on the page
     * @property {Function} create - Create card equalizer for specific container
     * @property {Function} equalize - Equalize cards in specific container
     * @property {Function} reset - Reset all card heights
     * @property {Map} instances - Map of initialized container instances
     */
    window.RSL.Cards = {
        /**
         * Track initialized instances
         */
        instances: instances,

        /**
         * Initialize all card equalizers on the page
         *
         * Scans for all .slot-layout elements containing cards with data-same-size="true"
         * and initializes card height equalization. This method is automatically called
         * by the JSON Layout API after rendering.
         *
         * @returns {void}
         *
         * @example
         * // JSON API compatible - Initialize all instances:
         * RSL.Cards.init();
         */
        init: function() {
            autoInit();
        },

        /**
         * Create card equalizer for a specific container
         *
         * @param {HTMLElement} container - The container element
         * @returns {Object} Instance object
         *
         * @example
         * const container = document.querySelector('.slot-layout');
         * RSL.Cards.create(container);
         */
        create: createCardEqualizer,

        /**
         * Equalize card heights in a specific container
         *
         * @param {HTMLElement} container - The container element
         * @returns {void}
         *
         * @example
         * const container = document.querySelector('.slot-layout');
         * RSL.Cards.equalize(container);
         */
        equalize: function(container) {
            adjustCardHeight(container);
        },

        /**
         * Reset all card heights to auto
         *
         * @returns {void}
         *
         * @example
         * RSL.Cards.reset();
         */
        reset: resetCardHeights,

        /**
         * Track FilterBus subscriptions
         */
        filterSubscriptions: filterSubscriptions,

        /**
         * Initialize FilterBus subscriptions for all cards
         *
         * @returns {void}
         *
         * @example
         * RSL.Cards.initFilterBus();
         */
        initFilterBus: initFilterBusCards,

        /**
         * Destroy FilterBus subscription for a specific card
         *
         * @param {HTMLElement} card - Card element to unsubscribe
         * @returns {void}
         *
         * @example
         * const card = document.querySelector('.card');
         * RSL.Cards.destroyFilterBus(card);
         */
        destroyFilterBus: destroyFilterBusSubscription
    };

    /**
     * Auto-initialize on DOM ready or immediately if already loaded
     */
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', autoInit);
    } else {
        autoInit();
    }

})();
