/* ============================================
   RSL KPI Cards Component
   Version: 2.0.0
   Description: Premium dashboard KPI cards with
   trend indicators, sparklines, count-up animations,
   and full accessibility support.

   FilterBus Date Range Support (v2.0.0):
   - Filter KPI data by date range from Date Picker
   - Supports aggregation: sum, average, count, min, max
   - Automatic value recalculation on date change
   ============================================ */

(function() {
    'use strict';

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

    /**
     * Track initialized KPI card instances
     * @type {Set<HTMLElement>}
     */
    const instances = new Set();

    /**
     * Default configuration
     */
    const DEFAULTS = {
        animationDuration: 1000,
        animationDelay: 0,
        decimalPlaces: 0,
        enableSparkline: true,
        sparklineHeight: 50
    };

    /**
     * Data attribute to config property mapping
     */
    const ATTR_MAP = {
        'data-value': 'value',
        'data-label': 'label',
        'data-trend': 'trend',
        'data-trend-value': 'trendValue',
        'data-comparison': 'comparison',
        'data-icon': 'icon',
        'data-sparkline': 'sparkline',
        'data-color-scheme': 'colorScheme',
        'data-color': 'colorScheme',  // Alias for convenience
        'data-size': 'size',
        'data-progress': 'progress',
        'data-progress-max': 'progressMax',
        'data-progress-label': 'progressLabel',
        'data-secondary-value': 'secondaryValue',
        'data-secondary-label': 'secondaryLabel',
        'data-prefix': 'prefix',
        'data-suffix': 'suffix',
        'data-animate': 'animate',
        'data-animation-duration': 'animationDuration',
        'data-decimal-places': 'decimalPlaces',
        'data-clickable': 'clickable',
        'data-href': 'href',
        'data-glow': 'glow',
        'data-accent': 'accent',
        'data-accent-value': 'accentValue',
        'data-loading': 'loading',
        // FilterBus integration
        'data-filter-subscribe': 'filterSubscribe',
        'data-filter-value': 'filterValue',
        // Date filtering support
        'data-filterbus-date-field': 'filterBusDateField',
        'data-values': 'values',
        'data-aggregate': 'aggregate',
        'data-original-value': 'originalValue'
    };

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

    /**
     * Track date range filter state per card
     * @type {Map<HTMLElement, Object>}
     */
    const dateFilterState = new Map();

    /**
     * Parse a date string into a Date object
     * Supports: ISO (YYYY-MM-DD), US (MM/DD/YYYY), European (DD/MM/YYYY)
     * @param {string} dateStr - Date string to parse
     * @returns {Date|null} Parsed date or null if invalid
     */
    function parseDate(dateStr) {
        if (!dateStr) return null;
        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 or M/D/YYYY
        var usMatch = str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
        if (usMatch) {
            var d = new Date(parseInt(usMatch[3]), parseInt(usMatch[1]) - 1, parseInt(usMatch[2]));
            if (!isNaN(d.getTime())) return d;
        }

        // European format: DD/MM/YYYY or D/M/YYYY (less common, try as fallback)
        var euMatch = str.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
        if (euMatch) {
            var d = new Date(parseInt(euMatch[3]), parseInt(euMatch[2]) - 1, parseInt(euMatch[1]));
            if (!isNaN(d.getTime())) return d;
        }

        // Try native Date parsing as last resort
        var d = new Date(str);
        return isNaN(d.getTime()) ? null : d;
    }

    /**
     * Aggregate an array of values
     * @param {Array<number>} values - Array of numeric values
     * @param {string} method - Aggregation method (sum, average, count, min, max)
     * @returns {number} Aggregated value
     */
    function aggregateValues(values, method) {
        if (!values || values.length === 0) return 0;

        switch (method) {
            case 'sum':
                return values.reduce(function(a, b) { return a + b; }, 0);
            case 'average':
            case 'avg':
                return values.reduce(function(a, b) { return a + b; }, 0) / values.length;
            case 'count':
                return values.length;
            case 'min':
                return Math.min.apply(null, values);
            case 'max':
                return Math.max.apply(null, values);
            default:
                return values.reduce(function(a, b) { return a + b; }, 0);
        }
    }

    /**
     * Parse a data attribute value
     * @param {string} value - Raw attribute value
     * @param {*} defaultValue - Default value if parsing fails
     * @returns {*} Parsed value
     */
    function parseDataValue(value, defaultValue) {
        if (value === null || value === undefined) return defaultValue;
        if (value === '' || value === 'true') return true;
        if (value === 'false') return false;

        // Try JSON parse for arrays/objects
        try {
            return JSON.parse(value);
        } catch (e) {
            // Only convert to number if the ENTIRE string is a valid number
            // This prevents "4,500+" from becoming 4 or "5-Star" from becoming 5
            var trimmed = value.trim();
            if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
                return parseFloat(trimmed);
            }
            // Return as string for values like "4,500+", "97%", "60-Day", "5-Star Reviews"
            return value;
        }
    }

    /**
     * Get configuration from element data attributes
     * @param {HTMLElement} element - KPI card element
     * @returns {Object} Configuration object
     */
    function getConfig(element) {
        var config = Object.assign({}, DEFAULTS);

        Object.keys(ATTR_MAP).forEach(function(attr) {
            var value = element.getAttribute(attr);
            if (value !== null) {
                config[ATTR_MAP[attr]] = parseDataValue(value, config[ATTR_MAP[attr]]);
            }
        });

        // Handle boolean attributes (presence = true)
        ['data-animate', 'data-clickable', 'data-glow', 'data-accent-value', 'data-loading'].forEach(function(attr) {
            if (element.hasAttribute(attr) && element.getAttribute(attr) === '') {
                var key = ATTR_MAP[attr];
                config[key] = true;
            }
        });

        return config;
    }

    /**
     * Format a number with locale formatting
     * @param {number} value - Number to format
     * @param {number} decimalPlaces - Decimal places
     * @returns {string} Formatted number
     */
    function formatNumber(value, decimalPlaces) {
        if (typeof value !== 'number') return value;

        // Check if value is large enough to abbreviate
        if (Math.abs(value) >= 1000000000) {
            return (value / 1000000000).toFixed(1) + 'B';
        } else if (Math.abs(value) >= 1000000) {
            return (value / 1000000).toFixed(1) + 'M';
        } else if (Math.abs(value) >= 1000) {
            return (value / 1000).toFixed(1) + 'K';
        }

        return value.toLocaleString(undefined, {
            minimumFractionDigits: decimalPlaces,
            maximumFractionDigits: decimalPlaces
        });
    }

    /**
     * Parse a value string to extract numeric value
     * @param {string} valueStr - Value string (e.g., "$124,500", "12.5%")
     * @returns {Object} Parsed value with prefix, number, and suffix
     */
    function parseValueString(valueStr) {
        if (typeof valueStr !== 'string') {
            return { prefix: '', number: valueStr, suffix: '', raw: valueStr };
        }

        var match = valueStr.match(/^([^0-9-]*)(-?[\d,]+\.?\d*)(.*)$/);
        if (match) {
            var numStr = match[2].replace(/,/g, '');
            return {
                prefix: match[1] || '',
                number: parseFloat(numStr) || 0,
                suffix: match[3] || '',
                raw: valueStr
            };
        }

        return { prefix: '', number: 0, suffix: '', raw: valueStr };
    }

    /**
     * Animate count-up effect
     * @param {HTMLElement} element - Element to animate
     * @param {number} startValue - Starting value
     * @param {number} endValue - Ending value
     * @param {number} duration - Animation duration in ms
     * @param {string} prefix - Value prefix
     * @param {string} suffix - Value suffix
     * @param {number} decimalPlaces - Decimal places
     */
    function animateCountUp(element, startValue, endValue, duration, prefix, suffix, decimalPlaces) {
        var startTime = null;
        var currentValue = startValue;

        function easeOutQuart(t) {
            return 1 - Math.pow(1 - t, 4);
        }

        function animate(timestamp) {
            if (!startTime) startTime = timestamp;
            var progress = Math.min((timestamp - startTime) / duration, 1);
            var easedProgress = easeOutQuart(progress);

            currentValue = startValue + (endValue - startValue) * easedProgress;

            var displayValue = formatNumber(currentValue, decimalPlaces);
            element.textContent = prefix + displayValue + suffix;

            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                element.textContent = prefix + formatNumber(endValue, decimalPlaces) + suffix;
                element.classList.add('animated');
            }
        }

        requestAnimationFrame(animate);
    }

    /**
     * Generate ARIA label for screen readers
     * @param {Object} config - KPI card configuration
     * @returns {string} Accessible label
     */
    function generateAriaLabel(config) {
        var parts = [];

        if (config.label) {
            parts.push(config.label);
        }

        if (config.value) {
            parts.push(config.value);
        }

        if (config.trend && config.trendValue) {
            var trendText = config.trend === 'up' ? 'up' :
                           config.trend === 'down' ? 'down' : 'unchanged';
            parts.push(trendText + ' ' + config.trendValue);
        }

        if (config.comparison) {
            parts.push(config.comparison);
        }

        if (config.progress !== undefined) {
            var max = config.progressMax || 100;
            var percent = Math.round((config.progress / max) * 100);
            parts.push(percent + '% progress');
        }

        return parts.join(', ');
    }

    /**
     * Build KPI card HTML structure from data attributes
     * @param {HTMLElement} element - KPI card container
     * @param {Object} config - Configuration object
     */
    function buildCardStructure(element, config) {
        // Clear existing content if rebuilding
        element.innerHTML = '';

        // Ensure the card has the required class for styling
        if (!element.classList.contains('rsl-kpi-card')) {
            element.classList.add('rsl-kpi-card');
        }

        // Handle loading state - show skeleton
        if (config.loading) {
            element.classList.add('rsl-kpi-loading');
            element.setAttribute('data-loading', 'true');
            element.setAttribute('aria-busy', 'true');
            element.setAttribute('aria-label', 'Loading...');

            // Build skeleton structure
            var skeletonHeader = document.createElement('div');
            skeletonHeader.className = 'rsl-kpi-header';

            var skeletonLabel = document.createElement('div');
            skeletonLabel.className = 'rsl-kpi-label rsl-kpi-skeleton';
            skeletonLabel.innerHTML = '&nbsp;';
            skeletonHeader.appendChild(skeletonLabel);

            var skeletonIcon = document.createElement('div');
            skeletonIcon.className = 'rsl-kpi-icon rsl-kpi-skeleton';
            skeletonHeader.appendChild(skeletonIcon);

            element.appendChild(skeletonHeader);

            var skeletonValue = document.createElement('div');
            skeletonValue.className = 'rsl-kpi-value-container';
            var skeletonValueInner = document.createElement('div');
            skeletonValueInner.className = 'rsl-kpi-value rsl-kpi-skeleton';
            skeletonValueInner.innerHTML = '&nbsp;';
            skeletonValue.appendChild(skeletonValueInner);
            element.appendChild(skeletonValue);

            var skeletonFooter = document.createElement('div');
            skeletonFooter.className = 'rsl-kpi-footer';
            var skeletonTrend = document.createElement('div');
            skeletonTrend.className = 'rsl-kpi-trend rsl-kpi-skeleton';
            skeletonTrend.innerHTML = '&nbsp;';
            skeletonFooter.appendChild(skeletonTrend);
            element.appendChild(skeletonFooter);

            return; // Don't build normal structure
        }

        // Apply color scheme if specified
        if (config.colorScheme) {
            element.setAttribute('data-color-scheme', config.colorScheme);
        }

        // Apply size variant
        if (config.size) {
            element.setAttribute('data-size', config.size);
        }

        // Apply accent style
        if (config.accent) {
            element.setAttribute('data-accent', config.accent);
        }

        // Apply glow effect
        if (config.glow) {
            element.setAttribute('data-glow', '');
        }

        // Set ARIA attributes for accessibility
        // All cards should be keyboard focusable for screen reader users
        element.setAttribute('tabindex', '0');
        element.setAttribute('role', config.clickable ? 'button' : 'article');
        element.setAttribute('aria-label', generateAriaLabel(config));

        // Build header (icon + label)
        var header = document.createElement('div');
        header.className = 'rsl-kpi-header';

        if (config.label) {
            var label = document.createElement('div');
            label.className = 'rsl-kpi-label';
            label.textContent = config.label;
            header.appendChild(label);
        }

        if (config.icon) {
            var iconWrapper = document.createElement('div');
            iconWrapper.className = 'rsl-kpi-icon';
            var icon = document.createElement('i');
            // Support both "fa-icon" and "fas fa-icon" formats
            if (config.icon.indexOf(' ') === -1 && config.icon.indexOf('fa-') === 0) {
                icon.className = 'fas ' + config.icon;
            } else {
                icon.className = config.icon;
            }
            icon.setAttribute('aria-hidden', 'true');
            iconWrapper.appendChild(icon);
            header.appendChild(iconWrapper);
        }

        if (config.label || config.icon) {
            element.appendChild(header);
        }

        // Build value container
        var valueContainer = document.createElement('div');
        valueContainer.className = 'rsl-kpi-value-container';

        if (config.value !== undefined) {
            var parsedValue = parseValueString(String(config.value));
            var prefix = config.prefix || parsedValue.prefix;
            var suffix = config.suffix || parsedValue.suffix;

            var valueEl = document.createElement('div');
            valueEl.className = 'rsl-kpi-value';

            if (config.animate && typeof parsedValue.number === 'number' && !isNaN(parsedValue.number)) {
                valueEl.setAttribute('data-animate', '');
                valueEl.textContent = prefix + '0' + suffix;

                // Trigger animation after a short delay
                setTimeout(function() {
                    animateCountUp(
                        valueEl,
                        0,
                        parsedValue.number,
                        config.animationDuration || DEFAULTS.animationDuration,
                        prefix,
                        suffix,
                        config.decimalPlaces || DEFAULTS.decimalPlaces
                    );
                }, config.animationDelay || 100);
            } else {
                valueEl.textContent = config.value;
            }

            valueContainer.appendChild(valueEl);
        }

        element.appendChild(valueContainer);

        // Build footer (trend + comparison)
        var footer = document.createElement('div');
        footer.className = 'rsl-kpi-footer';
        var hasFooterContent = false;

        if (config.trend || config.trendValue) {
            var trend = document.createElement('div');
            trend.className = 'rsl-kpi-trend';

            var direction = config.trend || 'neutral';
            trend.setAttribute('data-direction', direction);

            // Add trend icon
            var trendIcon = document.createElement('span');
            trendIcon.className = 'rsl-kpi-trend-icon';
            trendIcon.setAttribute('aria-hidden', 'true');
            trend.appendChild(trendIcon);

            // Add trend value
            if (config.trendValue) {
                var trendText = document.createTextNode(config.trendValue);
                trend.appendChild(trendText);
            }

            // Screen reader text
            var srText = document.createElement('span');
            srText.className = 'sr-only';
            srText.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;';
            srText.textContent = (direction === 'up' ? 'Increased' : direction === 'down' ? 'Decreased' : 'No change') +
                                (config.trendValue ? ' by ' + config.trendValue : '');
            trend.appendChild(srText);

            if (config.animate) {
                trend.setAttribute('data-animate', '');
                setTimeout(function() {
                    trend.classList.add('animated');
                }, (config.animationDuration || DEFAULTS.animationDuration) + 200);
            }

            footer.appendChild(trend);
            hasFooterContent = true;
        }

        if (config.comparison) {
            var comparison = document.createElement('div');
            comparison.className = 'rsl-kpi-comparison';
            comparison.textContent = config.comparison;
            footer.appendChild(comparison);
            hasFooterContent = true;
        }

        if (hasFooterContent) {
            element.appendChild(footer);
        }

        // Build progress bar
        if (config.progress !== undefined) {
            var progressContainer = document.createElement('div');
            progressContainer.className = 'rsl-kpi-progress';

            var progressBar = document.createElement('div');
            progressBar.className = 'rsl-kpi-progress-bar';
            progressBar.setAttribute('role', 'progressbar');
            progressBar.setAttribute('aria-valuenow', config.progress);
            progressBar.setAttribute('aria-valuemin', '0');
            progressBar.setAttribute('aria-valuemax', config.progressMax || 100);

            var progressFill = document.createElement('div');
            progressFill.className = 'rsl-kpi-progress-fill';

            var progressPercent = (config.progress / (config.progressMax || 100)) * 100;
            progressFill.style.setProperty('--progress-width', progressPercent + '%');

            if (config.animate) {
                progressFill.setAttribute('data-animate', '');
                progressFill.style.width = '0%';
                setTimeout(function() {
                    progressFill.classList.add('animated');
                    progressFill.style.width = progressPercent + '%';
                }, 300);
            } else {
                progressFill.style.width = progressPercent + '%';
            }

            progressBar.appendChild(progressFill);
            progressContainer.appendChild(progressBar);

            if (config.progressLabel) {
                var progressLabel = document.createElement('div');
                progressLabel.className = 'rsl-kpi-progress-label';

                var labelLeft = document.createElement('span');
                labelLeft.textContent = config.progressLabel;

                var labelRight = document.createElement('span');
                labelRight.textContent = Math.round(progressPercent) + '%';

                progressLabel.appendChild(labelLeft);
                progressLabel.appendChild(labelRight);
                progressContainer.appendChild(progressLabel);
            }

            element.appendChild(progressContainer);
        }

        // Build secondary value
        if (config.secondaryValue) {
            var secondary = document.createElement('div');
            secondary.className = 'rsl-kpi-secondary';

            var secondaryValue = document.createElement('span');
            secondaryValue.className = 'rsl-kpi-secondary-value';
            secondaryValue.textContent = config.secondaryValue;
            secondary.appendChild(secondaryValue);

            if (config.secondaryLabel) {
                var secondaryLabel = document.createElement('span');
                secondaryLabel.className = 'rsl-kpi-secondary-label';
                secondaryLabel.textContent = config.secondaryLabel;
                secondary.appendChild(secondaryLabel);
            }

            element.appendChild(secondary);
        }

        // Build sparkline
        if (config.sparkline && Array.isArray(config.sparkline) && config.sparkline.length > 0) {
            var sparklineContainer = document.createElement('div');
            sparklineContainer.className = 'rsl-kpi-sparkline-inline';
            sparklineContainer.setAttribute('data-rsl-chart', '');
            sparklineContainer.setAttribute('data-type', 'sparkline');
            sparklineContainer.setAttribute('data-values', JSON.stringify(config.sparkline));

            if (config.colorScheme) {
                sparklineContainer.setAttribute('data-color-scheme', config.colorScheme);
            }

            sparklineContainer.setAttribute('data-size', 'md');
            sparklineContainer.style.height = (config.sparklineHeight || DEFAULTS.sparklineHeight) + 'px';

            element.appendChild(sparklineContainer);

            // Initialize sparkline via Charts component
            setTimeout(function() {
                if (window.RSL && window.RSL.Charts && window.RSL.Charts.init) {
                    window.RSL.Charts.init();
                }
            }, 150);
        }

        // Handle click events
        if (config.clickable && config.href) {
            element.addEventListener('click', function() {
                window.location.href = config.href;
            });
            element.addEventListener('keydown', function(e) {
                if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault();
                    window.location.href = config.href;
                }
            });
        }
    }

    /**
     * Setup FilterBus subscription for a KPI card
     * @param {HTMLElement} element - KPI card element
     * @param {Object} config - Configuration object
     */
    function setupFilterBusSubscription(element, config) {
        // Check if FilterBus is available
        if (!window.RSL || !window.RSL.FilterBus) {
            return;
        }

        // Need either visibility filter or date filter
        if (!config.filterSubscribe && !config.filterBusDateField) {
            return;
        }

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

        // Build list of keys to subscribe to
        var subscribeKeys = [];

        // Add visibility filter key
        if (config.filterSubscribe) {
            // Support comma-separated keys
            var keys = config.filterSubscribe.split(',').map(function(k) { return k.trim(); });
            subscribeKeys = subscribeKeys.concat(keys);
        }

        // For date filtering, we need to subscribe to ALL keys (no filter)
        // so we can detect date objects from any date picker
        // If we have date filtering enabled, don't restrict to specific keys
        var subscribeOptions = {};
        if (!config.filterBusDateField && subscribeKeys.length > 0) {
            // Only visibility filtering - restrict to specific keys
            subscribeOptions = { keys: subscribeKeys };
        }
        // else: date filtering enabled - subscribe to all keys to catch date objects

        // Subscribe to FilterBus
        window.RSL.FilterBus.subscribe(
            subscriberId,
            function(state, meta) {
                handleFilterBusChange(element, config, state, meta);
            },
            subscribeOptions
        );

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

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

    /**
     * Handle FilterBus state changes
     * @param {HTMLElement} element - KPI card element
     * @param {Object} config - Configuration object
     * @param {Object} state - Current filter state
     * @param {Object} meta - Change metadata
     */
    function handleFilterBusChange(element, config, state, meta) {
        if (!state) return;

        // Handle date range filtering
        if (config.filterBusDateField && config.values) {
            handleDateRangeFilter(element, config, state, meta);
        }

        // Handle visibility filtering (existing behavior)
        if (config.filterSubscribe) {
            handleVisibilityFilter(element, config, state, meta);
        }
    }

    /**
     * Handle date range filtering - recalculate KPI value based on filtered data
     * @param {HTMLElement} element - KPI card element
     * @param {Object} config - Configuration object
     * @param {Object} state - Current filter state
     * @param {Object} meta - Change metadata
     */
    function handleDateRangeFilter(element, config, state, meta) {
        var dateField = config.filterBusDateField;
        var values = config.values;
        var aggregate = config.aggregate || 'sum';

        if (!Array.isArray(values) || values.length === 0) return;

        // Find date filter in state - check all keys for date objects
        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;
            }
        });

        // Store original value if not already stored
        if (!config.originalValue) {
            config.originalValue = config.value;
            element.setAttribute('data-original-value', config.value);
        }

        var filteredValues = [];

        if (dateFilter && dateFilter.startDate) {
            // Filter values by date range
            var filterStart = parseDate(dateFilter.startDate);
            var filterEnd = parseDate(dateFilter.endDate || dateFilter.startDate);

            if (filterStart && filterEnd) {
                // Make end date inclusive
                filterEnd.setHours(23, 59, 59, 999);

                values.forEach(function(item) {
                    var itemDate = parseDate(item[dateField] || item.date);
                    if (itemDate && itemDate >= filterStart && itemDate <= filterEnd) {
                        // Get the value to aggregate (look for 'value', 'amount', 'total', or first numeric)
                        var val = item.value !== undefined ? item.value :
                                  item.amount !== undefined ? item.amount :
                                  item.total !== undefined ? item.total : null;

                        if (val === null) {
                            // Find first numeric property
                            Object.keys(item).forEach(function(k) {
                                if (val === null && typeof item[k] === 'number' && k !== dateField) {
                                    val = item[k];
                                }
                            });
                        }

                        if (typeof val === 'number') {
                            filteredValues.push(val);
                        }
                    }
                });
            }
        } else {
            // No date filter - use all values
            values.forEach(function(item) {
                var val = item.value !== undefined ? item.value :
                          item.amount !== undefined ? item.amount :
                          item.total !== undefined ? item.total : null;

                if (val === null) {
                    Object.keys(item).forEach(function(k) {
                        if (val === null && typeof item[k] === 'number') {
                            val = item[k];
                        }
                    });
                }

                if (typeof val === 'number') {
                    filteredValues.push(val);
                }
            });
        }

        // Calculate new value
        var newValue = aggregateValues(filteredValues, aggregate);

        // Update the card value
        updateCardValue(element, config, newValue, filteredValues.length);

        // Dispatch event
        element.dispatchEvent(new CustomEvent('rsl-kpi-card:date-filter', {
            bubbles: true,
            detail: {
                dateFilter: dateFilter,
                filteredCount: filteredValues.length,
                totalCount: values.length,
                value: newValue
            }
        }));
    }

    /**
     * Update the displayed value on a KPI card
     * @param {HTMLElement} element - KPI card element
     * @param {Object} config - Configuration object
     * @param {number} newValue - New value to display
     * @param {number} count - Number of items in filtered data
     */
    function updateCardValue(element, config, newValue, count) {
        var valueEl = element.querySelector('.rsl-kpi-value');
        if (!valueEl) return;

        // Parse the original value to get prefix/suffix
        var parsed = parseValueString(String(config.originalValue || config.value));
        var prefix = config.prefix || parsed.prefix;
        var suffix = config.suffix || parsed.suffix;
        var decimalPlaces = config.decimalPlaces || DEFAULTS.decimalPlaces;

        // Format the new value
        var displayValue = prefix + formatNumber(newValue, decimalPlaces) + suffix;
        valueEl.textContent = displayValue;

        // Update aria-label
        element.setAttribute('aria-label', generateAriaLabel(Object.assign({}, config, { value: displayValue })));

        // Add indicator if filtered
        if (count < (config.values ? config.values.length : 0)) {
            element.classList.add('rsl-kpi-filtered');
        } else {
            element.classList.remove('rsl-kpi-filtered');
        }
    }

    /**
     * Handle visibility filtering (show/hide based on filter value)
     * @param {HTMLElement} element - KPI card element
     * @param {Object} config - Configuration object
     * @param {Object} state - Current filter state
     * @param {Object} meta - Change metadata
     */
    function handleVisibilityFilter(element, config, state, meta) {
        var filterKey = config.filterSubscribe;
        var filterValue = state[filterKey];
        var cardValue = config.filterValue;

        // Skip date objects - they're handled by handleDateRangeFilter
        if (filterValue && typeof filterValue === 'object' && filterValue.mode) {
            return;
        }

        // Determine visibility
        var shouldShow = true;

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

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

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

    /**
     * Calculate initial value from data array (no date filter applied)
     * @param {HTMLElement} element - KPI card element
     * @param {Object} config - Configuration object
     */
    function calculateInitialValue(element, config) {
        var values = config.values;
        var aggregate = config.aggregate || 'sum';

        if (!Array.isArray(values) || values.length === 0) return;

        // Extract all numeric values from the data array
        var numericValues = [];
        values.forEach(function(item) {
            var val = item.value !== undefined ? item.value :
                      item.amount !== undefined ? item.amount :
                      item.total !== undefined ? item.total : null;

            if (val === null) {
                Object.keys(item).forEach(function(k) {
                    if (val === null && typeof item[k] === 'number' && k !== config.filterBusDateField && k !== 'date') {
                        val = item[k];
                    }
                });
            }

            if (typeof val === 'number') {
                numericValues.push(val);
            }
        });

        if (numericValues.length === 0) return;

        // Calculate aggregated value
        var newValue = aggregateValues(numericValues, aggregate);

        // Store original config value
        config.originalValue = config.value;

        // Update the displayed value
        updateCardValue(element, config, newValue, numericValues.length);
    }

    /**
     * Initialize a single KPI card
     * @param {HTMLElement} element - KPI card element
     * @returns {Object} Instance object
     */
    function create(element) {
        if (!element) return null;

        // Prevent double-initialization
        if (instances.has(element)) {
            return { element: element, config: getConfig(element) };
        }

        var config = getConfig(element);

        // Only build structure if element is empty or has data-rsl-kpi
        if (element.innerHTML.trim() === '' || element.hasAttribute('data-rsl-kpi')) {
            buildCardStructure(element, config);
        }

        // Setup FilterBus subscription if configured
        setupFilterBusSubscription(element, config);

        // Calculate initial value from data array if present (before any date filter)
        if (config.filterBusDateField && config.values && Array.isArray(config.values) && config.values.length > 0) {
            calculateInitialValue(element, config);
        }

        // Add to instances
        instances.add(element);

        // Add initialized class after a brief delay to prevent transition flicker on page load
        setTimeout(function() {
            element.classList.add('rsl-kpi-initialized');
        }, 50);

        return { element: element, config: config };
    }

    /**
     * Initialize all KPI cards on the page
     */
    function init() {
        var cards = document.querySelectorAll('[data-rsl-kpi], .rsl-kpi-card[data-value]');
        cards.forEach(function(card) {
            create(card);
        });
    }

    /**
     * Update a KPI card with new values
     * @param {HTMLElement} element - KPI card element
     * @param {Object} newConfig - New configuration values
     */
    function update(element, newConfig) {
        if (!element) return;

        // Merge new config with existing attributes
        Object.keys(newConfig).forEach(function(key) {
            var attrName = Object.keys(ATTR_MAP).find(function(attr) {
                return ATTR_MAP[attr] === key;
            });
            if (attrName) {
                if (typeof newConfig[key] === 'object') {
                    element.setAttribute(attrName, JSON.stringify(newConfig[key]));
                } else {
                    element.setAttribute(attrName, newConfig[key]);
                }
            }
        });

        // Remove from instances to allow rebuild
        instances.delete(element);

        // Rebuild
        create(element);
    }

    /**
     * Set loading state
     * @param {HTMLElement} element - KPI card element
     * @param {boolean} isLoading - Loading state
     */
    function setLoading(element, isLoading) {
        if (isLoading) {
            element.setAttribute('data-loading', 'true');
            element.classList.add('rsl-kpi-loading');
        } else {
            element.removeAttribute('data-loading');
            element.classList.remove('rsl-kpi-loading');
        }
    }

    /**
     * Destroy a KPI card instance
     * @param {HTMLElement} element - KPI card element
     */
    function destroy(element) {
        if (!element) return;

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

        instances.delete(element);
        element.innerHTML = '';
        element.removeAttribute('role');
        element.removeAttribute('aria-label');
        element.removeAttribute('tabindex');
    }

    /**
     * Public API
     * @namespace RSL.KPICards
     */
    window.RSL.KPICards = {
        /**
         * Track initialized instances
         */
        instances: instances,

        /**
         * Initialize all KPI cards on the page
         * @returns {void}
         */
        init: init,

        /**
         * Create/initialize a single KPI card
         * @param {HTMLElement} element - The KPI card element
         * @returns {Object} Instance object
         */
        create: create,

        /**
         * Update a KPI card with new values
         * @param {HTMLElement} element - KPI card element
         * @param {Object} config - New configuration
         */
        update: update,

        /**
         * Set loading state on a KPI card
         * @param {HTMLElement} element - KPI card element
         * @param {boolean} isLoading - Loading state
         */
        setLoading: setLoading,

        /**
         * Destroy a KPI card instance
         * @param {HTMLElement} element - KPI card element
         */
        destroy: destroy,

        /**
         * Get configuration from element
         * @param {HTMLElement} element - KPI card element
         * @returns {Object} Configuration object
         */
        getConfig: getConfig
    };

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

})();
