/**
 * RSL Charts Component
 * Pure SVG-based chart rendering with zero dependencies
 * Fully accessible with keyboard navigation and screen reader support
 *
 * Accessibility Features:
 * - Hidden data tables for screen readers
 * - Keyboard navigation with arrow keys
 * - ARIA live regions for announcements
 * - Chart summaries with min/max/trend info
 * - High contrast mode support
 * - Focus indicators for data points
 *
 * FilterBus Date Range Support (v2.0.0):
 * - Filter chart data by date range from Date Picker
 * - Automatic re-render with filtered data
 * - Supports time-series data with date field
 *
 * @version 2.0.0
 */
(function() {
    'use strict';

    window.RSL = window.RSL || {};

    // Track initialized instances
    var instances = new Map();

    // Track FilterBus subscriptions for cleanup
    var filterSubscriptions = new Map();

    // Track original time series data for date filtering
    var originalTimeSeriesData = new Map();

    // Track charts currently being updated (prevent re-entry)
    var updatingCharts = new Set();

    // Live region for screen reader announcements
    var liveRegion = null;

    // Default configuration
    var DEFAULTS = {
        type: 'bar',
        width: null, // auto
        height: 300,
        padding: { top: 40, right: 20, bottom: 40, left: 50 },
        animate: true,
        animationDuration: 800,
        showTooltip: true,
        showLegend: false,
        showGrid: true,
        showAxis: true,
        showValues: false,
        colorScheme: 'primary',
        responsive: true,
        stacked: false,
        horizontal: false,
        curved: true, // for line/area charts
        innerRadius: 0, // for doughnut (0-1 ratio)
        sparkline: false
    };

    // Color schemes
    var COLOR_SCHEMES = {
        primary: ['#6E7BFF', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4', '#84CC16'],
        success: ['#10B981', '#34D399', '#6EE7B7', '#A7F3D0', '#D1FAE5', '#ECFDF5', '#059669', '#047857'],
        warm: ['#EF4444', '#F97316', '#F59E0B', '#FBBF24', '#FCD34D', '#FDE68A', '#DC2626', '#C2410C'],
        cool: ['#06B6D4', '#0EA5E9', '#3B82F6', '#6366F1', '#8B5CF6', '#A855F7', '#0891B2', '#0284C7'],
        mono: ['#6B7280', '#4B5563', '#9CA3AF', '#374151', '#D1D5DB', '#1F2937', '#A3A3A3', '#525252'],
        rainbow: ['#EF4444', '#F97316', '#FBBF24', '#22C55E', '#06B6D4', '#3B82F6', '#8B5CF6', '#EC4899'],
        accessible: ['#0077B6', '#E36414', '#2A9D8F', '#9B2226', '#7209B7', '#3A86FF', '#FF006E', '#8338EC']
    };

    /**
     * Parse data attribute value (handles JSON and simple values)
     */
    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) {
            // Check if it's a comma-separated list (for labels)
            if (typeof value === 'string' && value.indexOf(',') !== -1) {
                return value.split(',').map(function(s) { return s.trim(); });
            }
            // Return as string or number
            var num = parseFloat(value);
            return isNaN(num) ? value : num;
        }
    }

    /**
     * Get configuration from data attributes
     */
    function getConfig(element) {
        var config = Object.assign({}, DEFAULTS);

        // Map data attributes to config
        var attrMap = {
            'data-type': 'type',
            'data-values': 'values',
            'data-labels': 'labels',
            'data-datasets': 'datasets',
            'data-title': 'title',
            'data-subtitle': 'subtitle',
            'data-width': 'width',
            'data-height': 'height',
            'data-animate': 'animate',
            'data-animation-duration': 'animationDuration',
            'data-show-tooltip': 'showTooltip',
            'data-show-legend': 'showLegend',
            'data-show-grid': 'showGrid',
            'data-show-axis': 'showAxis',
            'data-show-values': 'showValues',
            'data-color-scheme': 'colorScheme',
            'data-colors': 'colors',
            'data-responsive': 'responsive',
            'data-stacked': 'stacked',
            'data-horizontal': 'horizontal',
            'data-curved': 'curved',
            'data-inner-radius': 'innerRadius',
            'data-sparkline': 'sparkline',
            'data-min': 'min',
            'data-max': 'max',
            'data-y-label': 'yLabel',
            'data-x-label': 'xLabel',
            'data-legend-position': 'legendPosition',
            'data-size': 'size',
            // FilterBus integration
            'data-filter-subscribe': 'filterSubscribe',
            'data-filter-value': 'filterValue',
            // Date filtering support
            'data-filterbus-date-field': 'filterBusDateField',
            'data-time-series-data': 'timeSeriesData'
        };

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

        // Handle boolean attributes (presence = true)
        ['data-animate', 'data-show-tooltip', 'data-show-legend', 'data-show-grid',
         'data-show-axis', 'data-show-values', 'data-stacked', 'data-horizontal',
         'data-curved', 'data-sparkline', 'data-responsive'].forEach(function(attr) {
            if (element.hasAttribute(attr) && element.getAttribute(attr) === '') {
                var key = attrMap[attr];
                config[key] = true;
            }
        });

        return config;
    }

    /**
     * Get colors for chart
     */
    function getColors(config, count) {
        if (config.colors && Array.isArray(config.colors)) {
            return config.colors;
        }
        var scheme = COLOR_SCHEMES[config.colorScheme] || COLOR_SCHEMES.primary;
        var colors = [];
        for (var i = 0; i < count; i++) {
            colors.push(scheme[i % scheme.length]);
        }
        return colors;
    }

    /**
     * Calculate nice axis values
     */
    function calculateAxisValues(min, max, steps) {
        steps = steps || 5;
        var range = max - min;
        var step = range / steps;

        // Round step to nice number
        var magnitude = Math.pow(10, Math.floor(Math.log10(step)));
        var residual = step / magnitude;

        if (residual > 5) step = 10 * magnitude;
        else if (residual > 2) step = 5 * magnitude;
        else if (residual > 1) step = 2 * magnitude;
        else step = magnitude;

        var niceMin = Math.floor(min / step) * step;
        var niceMax = Math.ceil(max / step) * step;

        var values = [];
        for (var v = niceMin; v <= niceMax; v += step) {
            values.push(Math.round(v * 100) / 100);
        }

        return { min: niceMin, max: niceMax, step: step, values: values };
    }

    /**
     * Format number for display
     */
    function formatNumber(value) {
        if (Math.abs(value) >= 1000000) {
            return (value / 1000000).toFixed(1) + 'M';
        }
        if (Math.abs(value) >= 1000) {
            return (value / 1000).toFixed(1) + 'K';
        }
        return value.toFixed(value % 1 === 0 ? 0 : 1);
    }

    /**
     * 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
        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;
    }

    /**
     * Filter time series data by date range
     * @param {Array} data - Array of {date, value} or {date, label, value} objects
     * @param {string} dateField - Field name containing the date
     * @param {Object} dateFilter - Filter object with startDate and endDate
     * @returns {Object} Filtered {values, labels} arrays
     */
    function filterTimeSeriesData(data, dateField, dateFilter) {
        if (!data || !Array.isArray(data) || data.length === 0) {
            return { values: [], labels: [] };
        }

        var filterStart = parseDate(dateFilter.startDate);
        var filterEnd = parseDate(dateFilter.endDate || dateFilter.startDate);

        if (!filterStart || !filterEnd) {
            // No valid filter, return all data
            return extractValuesAndLabels(data, dateField);
        }

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

        var filtered = data.filter(function(item) {
            var itemDate = parseDate(item[dateField] || item.date);
            return itemDate && itemDate >= filterStart && itemDate <= filterEnd;
        });

        return extractValuesAndLabels(filtered, dateField);
    }

    /**
     * Extract values and labels from time series data
     * @param {Array} data - Array of data objects
     * @param {string} dateField - Field name containing the date
     * @returns {Object} {values, labels} arrays
     */
    function extractValuesAndLabels(data, dateField) {
        var values = [];
        var labels = [];

        data.forEach(function(item) {
            // Get value - 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) {
                Object.keys(item).forEach(function(k) {
                    if (val === null && typeof item[k] === 'number' && k !== dateField && k !== 'date') {
                        val = item[k];
                    }
                });
            }

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

                // Get label - use 'label' field or formatted date
                var label = item.label;
                if (!label) {
                    var dateVal = item[dateField] || item.date;
                    if (dateVal) {
                        var d = parseDate(dateVal);
                        if (d) {
                            // Format as short date (MM/DD)
                            label = (d.getMonth() + 1) + '/' + d.getDate();
                        }
                    }
                }
                labels.push(label || '');
            }
        });

        return { values: values, labels: labels };
    }

    /**
     * Create SVG element
     */
    function createSVG(tag, attrs) {
        var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
        if (attrs) {
            Object.keys(attrs).forEach(function(key) {
                el.setAttribute(key, attrs[key]);
            });
        }
        return el;
    }

    /**
     * Get or create live region for screen reader announcements
     */
    function getLiveRegion() {
        if (!liveRegion) {
            liveRegion = document.createElement('div');
            liveRegion.setAttribute('aria-live', 'polite');
            liveRegion.setAttribute('aria-atomic', 'true');
            liveRegion.className = 'rsl-chart-sr-only rsl-chart-live-region';
            liveRegion.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;';
            document.body.appendChild(liveRegion);
        }
        return liveRegion;
    }

    /**
     * Announce message to screen readers
     */
    function announce(message) {
        var region = getLiveRegion();
        // Clear and set to trigger announcement
        region.textContent = '';
        setTimeout(function() {
            region.textContent = message;
        }, 50);
    }

    /**
     * Generate chart summary for screen readers
     */
    function generateChartSummary(config, values, labels) {
        var type = config.type;
        var title = config.title || 'Chart';
        var flatValues = Array.isArray(values[0]) ? values.flat() : values;

        var min = Math.min.apply(null, flatValues);
        var max = Math.max.apply(null, flatValues);
        var sum = flatValues.reduce(function(a, b) { return a + b; }, 0);
        var avg = sum / flatValues.length;

        var summary = title + '. ';
        summary += type.charAt(0).toUpperCase() + type.slice(1) + ' chart with ' + flatValues.length + ' data points. ';
        summary += 'Values range from ' + formatNumber(min) + ' to ' + formatNumber(max) + '. ';
        summary += 'Average value: ' + formatNumber(avg) + '. ';

        // Add trend for line/area charts
        if (type === 'line' || type === 'area') {
            var first = flatValues[0];
            var last = flatValues[flatValues.length - 1];
            if (last > first) {
                summary += 'Overall trend: increasing by ' + formatNumber(last - first) + '.';
            } else if (last < first) {
                summary += 'Overall trend: decreasing by ' + formatNumber(first - last) + '.';
            } else {
                summary += 'Overall trend: stable.';
            }
        }

        return summary;
    }

    /**
     * Create accessible data table for screen readers
     */
    function createAccessibleTable(container, config, values, labels) {
        var table = document.createElement('table');
        table.className = 'rsl-chart-sr-only rsl-chart-data-table';
        table.setAttribute('role', 'table');

        // Caption with chart title and summary
        var caption = document.createElement('caption');
        caption.textContent = generateChartSummary(config, values, labels);
        table.appendChild(caption);

        var thead = document.createElement('thead');
        var headerRow = document.createElement('tr');

        // Determine table structure based on chart type
        var isPieOrDoughnut = config.type === 'pie' || config.type === 'doughnut';
        var isMultiSeries = config.datasets && config.datasets.length > 0;

        if (isPieOrDoughnut) {
            // Pie/Doughnut: Category, Value, Percentage
            ['Category', 'Value', 'Percentage'].forEach(function(text) {
                var th = document.createElement('th');
                th.setAttribute('scope', 'col');
                th.textContent = text;
                headerRow.appendChild(th);
            });
            thead.appendChild(headerRow);
            table.appendChild(thead);

            var tbody = document.createElement('tbody');
            var total = values.reduce(function(a, b) { return a + b; }, 0);

            values.forEach(function(value, i) {
                var row = document.createElement('tr');

                var labelCell = document.createElement('th');
                labelCell.setAttribute('scope', 'row');
                labelCell.textContent = labels[i] || 'Item ' + (i + 1);
                row.appendChild(labelCell);

                var valueCell = document.createElement('td');
                valueCell.textContent = formatNumber(value);
                row.appendChild(valueCell);

                var percentCell = document.createElement('td');
                percentCell.textContent = ((value / total) * 100).toFixed(1) + '%';
                row.appendChild(percentCell);

                tbody.appendChild(row);
            });
            table.appendChild(tbody);

        } else if (isMultiSeries) {
            // Multi-series: Label column + one column per series
            var labelHeader = document.createElement('th');
            labelHeader.setAttribute('scope', 'col');
            labelHeader.textContent = config.xLabel || 'Category';
            headerRow.appendChild(labelHeader);

            config.datasets.forEach(function(dataset) {
                var th = document.createElement('th');
                th.setAttribute('scope', 'col');
                th.textContent = dataset.label || 'Series';
                headerRow.appendChild(th);
            });
            thead.appendChild(headerRow);
            table.appendChild(thead);

            var tbody = document.createElement('tbody');
            var numRows = config.datasets[0].values.length;

            for (var i = 0; i < numRows; i++) {
                var row = document.createElement('tr');

                var labelCell = document.createElement('th');
                labelCell.setAttribute('scope', 'row');
                labelCell.textContent = labels[i] || 'Item ' + (i + 1);
                row.appendChild(labelCell);

                config.datasets.forEach(function(dataset) {
                    var valueCell = document.createElement('td');
                    valueCell.textContent = formatNumber(dataset.values[i]);
                    row.appendChild(valueCell);
                });

                tbody.appendChild(row);
            }
            table.appendChild(tbody);

        } else {
            // Single series: Label, Value
            var labelHeader = document.createElement('th');
            labelHeader.setAttribute('scope', 'col');
            labelHeader.textContent = config.xLabel || 'Category';
            headerRow.appendChild(labelHeader);

            var valueHeader = document.createElement('th');
            valueHeader.setAttribute('scope', 'col');
            valueHeader.textContent = config.yLabel || 'Value';
            headerRow.appendChild(valueHeader);

            thead.appendChild(headerRow);
            table.appendChild(thead);

            var tbody = document.createElement('tbody');
            var dataValues = Array.isArray(values[0]) ? values[0] : values;

            dataValues.forEach(function(value, i) {
                var row = document.createElement('tr');

                var labelCell = document.createElement('th');
                labelCell.setAttribute('scope', 'row');
                labelCell.textContent = labels[i] || 'Item ' + (i + 1);
                row.appendChild(labelCell);

                var valueCell = document.createElement('td');
                valueCell.textContent = formatNumber(value);
                row.appendChild(valueCell);

                tbody.appendChild(row);
            });
            table.appendChild(tbody);
        }

        // Insert table before SVG
        container.insertBefore(table, container.firstChild);

        return table;
    }

    /**
     * Setup keyboard navigation for chart data points
     */
    function setupKeyboardNavigation(container, dataPoints, config, values, labels) {
        if (!dataPoints || dataPoints.length === 0) return;

        var currentIndex = -1;
        var pointsArray = Array.from(dataPoints);

        // Make container focusable
        container.setAttribute('tabindex', '0');
        container.setAttribute('role', 'application');
        container.setAttribute('aria-label', (config.title || 'Chart') + '. Use arrow keys to navigate data points. Press Escape to exit.');

        // Add focus styles and tabindex to each point
        pointsArray.forEach(function(point, index) {
            point.setAttribute('tabindex', '-1');
            point.setAttribute('role', 'button');
            point.setAttribute('aria-label', getPointLabel(point, index, config, values, labels));
            point.style.cursor = 'pointer';
            point.style.outline = 'none';

            // Add focus event
            point.addEventListener('focus', function() {
                currentIndex = index;
                highlightPoint(point);
                announce(getPointLabel(point, index, config, values, labels));
            });

            // Add blur event
            point.addEventListener('blur', function() {
                unhighlightPoint(point);
            });
        });

        // Container keyboard handler
        container.addEventListener('keydown', function(e) {
            var handled = false;

            switch (e.key) {
                case 'ArrowRight':
                case 'ArrowDown':
                    e.preventDefault();
                    handled = true;
                    currentIndex = (currentIndex + 1) % pointsArray.length;
                    pointsArray[currentIndex].focus();
                    break;

                case 'ArrowLeft':
                case 'ArrowUp':
                    e.preventDefault();
                    handled = true;
                    currentIndex = currentIndex <= 0 ? pointsArray.length - 1 : currentIndex - 1;
                    pointsArray[currentIndex].focus();
                    break;

                case 'Home':
                    e.preventDefault();
                    handled = true;
                    currentIndex = 0;
                    pointsArray[currentIndex].focus();
                    break;

                case 'End':
                    e.preventDefault();
                    handled = true;
                    currentIndex = pointsArray.length - 1;
                    pointsArray[currentIndex].focus();
                    break;

                case 'Enter':
                case ' ':
                    e.preventDefault();
                    handled = true;
                    if (currentIndex >= 0) {
                        var point = pointsArray[currentIndex];
                        var label = getPointLabel(point, currentIndex, config, values, labels);
                        announce('Selected: ' + label);
                    }
                    break;

                case 'Escape':
                    e.preventDefault();
                    handled = true;
                    container.blur();
                    announce('Exited chart navigation');
                    break;
            }

            if (handled) {
                e.stopPropagation();
            }
        });

        // Focus handler for container
        container.addEventListener('focus', function(e) {
            // Only if focus came from outside (not from a data point)
            if (e.target === container && currentIndex === -1) {
                announce((config.title || 'Chart') + '. ' + pointsArray.length + ' data points. Use arrow keys to navigate.');
            }
        });
    }

    /**
     * Get accessible label for a data point
     */
    function getPointLabel(point, index, config, values, labels) {
        var label = point.getAttribute('data-label') || labels[index] || 'Item ' + (index + 1);
        var value = point.getAttribute('data-value');
        var percentage = point.getAttribute('data-percentage');
        var series = point.getAttribute('data-series');

        var text = label + ': ' + formatNumber(parseFloat(value));
        if (percentage) {
            text += ' (' + percentage + ')';
        }
        if (series) {
            text += ' in ' + series;
        }
        text += '. Item ' + (index + 1) + ' of ' + (labels.length || values.length);

        return text;
    }

    /**
     * Highlight a data point (visual focus indicator)
     */
    function highlightPoint(point) {
        var tagName = point.tagName.toLowerCase();
        if (tagName === 'rect') {
            point.setAttribute('data-original-stroke', point.getAttribute('stroke') || 'none');
            point.setAttribute('data-original-stroke-width', point.getAttribute('stroke-width') || '0');
            point.setAttribute('stroke', '#000');
            point.setAttribute('stroke-width', '3');
        } else if (tagName === 'circle') {
            point.setAttribute('data-original-r', point.getAttribute('r'));
            point.setAttribute('r', parseFloat(point.getAttribute('r')) + 3);
        } else if (tagName === 'path') {
            point.setAttribute('data-original-stroke', point.getAttribute('stroke') || 'none');
            point.setAttribute('data-original-stroke-width', point.getAttribute('stroke-width') || '0');
            point.setAttribute('stroke', '#000');
            point.setAttribute('stroke-width', '3');
            point.setAttribute('data-original-transform', point.getAttribute('transform') || '');
            point.setAttribute('transform', (point.getAttribute('transform') || '') + ' scale(1.05)');
        }
    }

    /**
     * Remove highlight from data point
     */
    function unhighlightPoint(point) {
        var tagName = point.tagName.toLowerCase();
        if (tagName === 'rect') {
            point.setAttribute('stroke', point.getAttribute('data-original-stroke') || 'none');
            point.setAttribute('stroke-width', point.getAttribute('data-original-stroke-width') || '0');
        } else if (tagName === 'circle') {
            var originalR = point.getAttribute('data-original-r');
            if (originalR) {
                point.setAttribute('r', originalR);
            }
        } else if (tagName === 'path') {
            point.setAttribute('stroke', point.getAttribute('data-original-stroke') || 'none');
            point.setAttribute('stroke-width', point.getAttribute('data-original-stroke-width') || '0');
            point.setAttribute('transform', point.getAttribute('data-original-transform') || '');
        }
    }

    /**
     * Render Bar Chart
     */
    function renderBarChart(container, config) {
        var values = config.values || [];
        var labels = config.labels || [];
        var datasets = config.datasets;

        // Handle multi-series data
        var isMultiSeries = datasets && datasets.length > 0;
        if (isMultiSeries) {
            values = datasets.map(function(d) { return d.values; });
            labels = config.labels || [];
        } else {
            values = [values];
        }

        var width = config.width || container.offsetWidth || 400;
        var height = config.height;
        var padding = config.padding;

        // Calculate chart area
        var chartWidth = width - padding.left - padding.right;
        var chartHeight = height - padding.top - padding.bottom;

        // Find min/max
        var allValues = values.flat();
        var dataMin = config.min !== undefined ? config.min : Math.min(0, Math.min.apply(null, allValues));
        var dataMax = config.max !== undefined ? config.max : Math.max.apply(null, allValues);
        var axis = calculateAxisValues(dataMin, dataMax);

        // Create SVG
        var svg = createSVG('svg', {
            'class': 'rsl-chart-svg',
            'viewBox': '0 0 ' + width + ' ' + height,
            'preserveAspectRatio': 'xMidYMid meet',
            'role': 'img',
            'aria-label': config.title || 'Bar Chart'
        });

        // Add title for accessibility
        if (config.title) {
            var titleEl = createSVG('title');
            titleEl.textContent = config.title;
            svg.appendChild(titleEl);
        }

        var g = createSVG('g', { 'transform': 'translate(' + padding.left + ',' + padding.top + ')' });
        svg.appendChild(g);

        // Draw grid
        if (config.showGrid) {
            var gridGroup = createSVG('g', { 'class': 'rsl-chart-grid' });
            axis.values.forEach(function(v) {
                var y = chartHeight - ((v - axis.min) / (axis.max - axis.min)) * chartHeight;
                var line = createSVG('line', {
                    'x1': 0, 'y1': y, 'x2': chartWidth, 'y2': y,
                    'stroke': 'var(--rsl-chart-grid-color, #e5e7eb)',
                    'stroke-dasharray': '4,4'
                });
                gridGroup.appendChild(line);
            });
            g.appendChild(gridGroup);
        }

        // Draw axes
        if (config.showAxis) {
            var axisGroup = createSVG('g', { 'class': 'rsl-chart-axis' });

            // Y-axis
            var yAxis = createSVG('line', {
                'x1': 0, 'y1': 0, 'x2': 0, 'y2': chartHeight,
                'stroke': 'var(--rsl-chart-axis-color, #6b7280)'
            });
            axisGroup.appendChild(yAxis);

            // Y-axis labels
            axis.values.forEach(function(v) {
                var y = chartHeight - ((v - axis.min) / (axis.max - axis.min)) * chartHeight;
                var label = createSVG('text', {
                    'x': -10, 'y': y + 4,
                    'text-anchor': 'end',
                    'class': 'rsl-chart-axis-label',
                    'fill': 'var(--rsl-chart-text-color, #6b7280)',
                    'font-size': '12'
                });
                label.textContent = formatNumber(v);
                axisGroup.appendChild(label);
            });

            // X-axis
            var xAxis = createSVG('line', {
                'x1': 0, 'y1': chartHeight, 'x2': chartWidth, 'y2': chartHeight,
                'stroke': 'var(--rsl-chart-axis-color, #6b7280)'
            });
            axisGroup.appendChild(xAxis);

            g.appendChild(axisGroup);
        }

        // Calculate bar dimensions
        var numBars = labels.length || values[0].length;
        var numSeries = values.length;
        var groupWidth = chartWidth / numBars;
        var barPadding = groupWidth * 0.2;
        var barWidth = config.stacked ? (groupWidth - barPadding) : ((groupWidth - barPadding) / numSeries);

        var colors = getColors(config, numSeries);

        // Draw bars
        var barsGroup = createSVG('g', { 'class': 'rsl-chart-bars' });

        values.forEach(function(series, seriesIndex) {
            series.forEach(function(value, i) {
                var barHeight = ((value - axis.min) / (axis.max - axis.min)) * chartHeight;
                var x, y;

                if (config.stacked) {
                    // Calculate stacked position
                    var stackOffset = 0;
                    for (var s = 0; s < seriesIndex; s++) {
                        stackOffset += ((values[s][i] - axis.min) / (axis.max - axis.min)) * chartHeight;
                    }
                    x = i * groupWidth + barPadding / 2;
                    y = chartHeight - stackOffset - barHeight;
                } else {
                    x = i * groupWidth + barPadding / 2 + seriesIndex * barWidth;
                    y = chartHeight - barHeight;
                }

                var bar = createSVG('rect', {
                    'class': 'rsl-chart-bar',
                    'x': x,
                    'y': config.animate ? chartHeight : y,
                    'width': barWidth - 2,
                    'height': config.animate ? 0 : barHeight,
                    'fill': colors[seriesIndex],
                    'rx': '4',
                    'data-value': value,
                    'data-label': labels[i] || '',
                    'data-series': datasets ? datasets[seriesIndex].label : '',
                    'style': config.animate ? 'animation-delay: ' + (i * 50) + 'ms' : ''
                });

                // Animation
                if (config.animate) {
                    bar.style.transition = 'y ' + config.animationDuration + 'ms ease-out, height ' + config.animationDuration + 'ms ease-out';
                    setTimeout(function() {
                        bar.setAttribute('y', y);
                        bar.setAttribute('height', barHeight);
                    }, 50 + i * 50);
                }

                barsGroup.appendChild(bar);

                // Value labels
                if (config.showValues) {
                    var valueLabel = createSVG('text', {
                        'x': x + (barWidth - 2) / 2,
                        'y': y - 5,
                        'text-anchor': 'middle',
                        'class': 'rsl-chart-value-label',
                        'fill': 'var(--rsl-chart-text-color, #374151)',
                        'font-size': '11',
                        'font-weight': '600'
                    });
                    valueLabel.textContent = formatNumber(value);
                    if (config.animate) {
                        valueLabel.style.opacity = '0';
                        setTimeout(function() {
                            valueLabel.style.transition = 'opacity 300ms';
                            valueLabel.style.opacity = '1';
                        }, config.animationDuration + i * 50);
                    }
                    barsGroup.appendChild(valueLabel);
                }
            });
        });

        g.appendChild(barsGroup);

        // X-axis labels
        if (config.showAxis && labels.length > 0) {
            var xLabelsGroup = createSVG('g', { 'class': 'rsl-chart-x-labels' });
            labels.forEach(function(label, i) {
                var x = i * groupWidth + groupWidth / 2;
                var text = createSVG('text', {
                    'x': x,
                    'y': chartHeight + 20,
                    'text-anchor': 'middle',
                    'class': 'rsl-chart-axis-label',
                    'fill': 'var(--rsl-chart-text-color, #6b7280)',
                    'font-size': '12'
                });
                text.textContent = label;
                xLabelsGroup.appendChild(text);
            });
            g.appendChild(xLabelsGroup);
        }

        // Title
        if (config.title && !config.sparkline) {
            var titleText = createSVG('text', {
                'x': width / 2,
                'y': 20,
                'text-anchor': 'middle',
                'class': 'rsl-chart-title',
                'fill': 'var(--rsl-chart-text-color, #111827)',
                'font-size': '16',
                'font-weight': '600'
            });
            titleText.textContent = config.title;
            svg.appendChild(titleText);
        }

        container.appendChild(svg);

        // Add tooltip functionality
        if (config.showTooltip) {
            addTooltipListeners(container, barsGroup.querySelectorAll('.rsl-chart-bar'));
        }

        // Accessibility: Add hidden data table for screen readers
        var flatValues = values.flat();
        createAccessibleTable(container, config, flatValues, labels);

        // Note: SVG is NOT aria-hidden because it contains focusable elements for keyboard nav
        // The accessible table provides redundant data access for screen readers

        // Set up keyboard navigation on bars
        var bars = barsGroup.querySelectorAll('.rsl-chart-bar');
        setupKeyboardNavigation(container, bars, config, flatValues, labels);

        return svg;
    }

    /**
     * Render Line Chart
     */
    function renderLineChart(container, config, isArea) {
        var values = config.values || [];
        var labels = config.labels || [];
        var datasets = config.datasets;

        // Handle multi-series data
        var isMultiSeries = datasets && datasets.length > 0;
        if (isMultiSeries) {
            values = datasets.map(function(d) { return d.values; });
        } else {
            values = [values];
        }

        var width = config.width || container.offsetWidth || 400;
        var height = config.height;
        var padding = config.padding;

        var chartWidth = width - padding.left - padding.right;
        var chartHeight = height - padding.top - padding.bottom;

        // Find min/max
        var allValues = values.flat();
        var dataMin = config.min !== undefined ? config.min : Math.min(0, Math.min.apply(null, allValues));
        var dataMax = config.max !== undefined ? config.max : Math.max.apply(null, allValues);
        var axis = calculateAxisValues(dataMin, dataMax);

        var svg = createSVG('svg', {
            'class': 'rsl-chart-svg',
            'viewBox': '0 0 ' + width + ' ' + height,
            'preserveAspectRatio': 'xMidYMid meet',
            'role': 'img',
            'aria-label': config.title || (isArea ? 'Area Chart' : 'Line Chart')
        });

        if (config.title) {
            var titleEl = createSVG('title');
            titleEl.textContent = config.title;
            svg.appendChild(titleEl);
        }

        var g = createSVG('g', { 'transform': 'translate(' + padding.left + ',' + padding.top + ')' });
        svg.appendChild(g);

        // Draw grid
        if (config.showGrid) {
            var gridGroup = createSVG('g', { 'class': 'rsl-chart-grid' });
            axis.values.forEach(function(v) {
                var y = chartHeight - ((v - axis.min) / (axis.max - axis.min)) * chartHeight;
                var line = createSVG('line', {
                    'x1': 0, 'y1': y, 'x2': chartWidth, 'y2': y,
                    'stroke': 'var(--rsl-chart-grid-color, #e5e7eb)',
                    'stroke-dasharray': '4,4'
                });
                gridGroup.appendChild(line);
            });
            g.appendChild(gridGroup);
        }

        // Draw axes
        if (config.showAxis) {
            var axisGroup = createSVG('g', { 'class': 'rsl-chart-axis' });

            var yAxis = createSVG('line', {
                'x1': 0, 'y1': 0, 'x2': 0, 'y2': chartHeight,
                'stroke': 'var(--rsl-chart-axis-color, #6b7280)'
            });
            axisGroup.appendChild(yAxis);

            axis.values.forEach(function(v) {
                var y = chartHeight - ((v - axis.min) / (axis.max - axis.min)) * chartHeight;
                var label = createSVG('text', {
                    'x': -10, 'y': y + 4,
                    'text-anchor': 'end',
                    'class': 'rsl-chart-axis-label',
                    'fill': 'var(--rsl-chart-text-color, #6b7280)',
                    'font-size': '12'
                });
                label.textContent = formatNumber(v);
                axisGroup.appendChild(label);
            });

            var xAxis = createSVG('line', {
                'x1': 0, 'y1': chartHeight, 'x2': chartWidth, 'y2': chartHeight,
                'stroke': 'var(--rsl-chart-axis-color, #6b7280)'
            });
            axisGroup.appendChild(xAxis);

            g.appendChild(axisGroup);
        }

        var colors = getColors(config, values.length);
        var numPoints = values[0].length;
        var stepX = numPoints > 1 ? chartWidth / (numPoints - 1) : chartWidth;

        // Draw lines/areas
        values.forEach(function(series, seriesIndex) {
            var points = [];
            series.forEach(function(value, i) {
                // For single point, center it horizontally
                var x = numPoints === 1 ? chartWidth / 2 : i * stepX;
                var y = chartHeight - ((value - axis.min) / (axis.max - axis.min)) * chartHeight;
                points.push({ x: x, y: y, value: value });
            });

            // Create path
            var pathD = '';
            if (points.length === 1) {
                // Single point - create a tiny line segment so it's visible
                // (SVG path with just M doesn't render anything)
                var px = points[0].x;
                var py = points[0].y;
                pathD = 'M ' + px + ',' + py + ' L ' + (px + 0.1) + ',' + py;
            } else if (config.curved && points.length > 2) {
                // Bezier curve
                pathD = 'M ' + points[0].x + ',' + points[0].y;
                for (var i = 1; i < points.length; i++) {
                    var prev = points[i - 1];
                    var curr = points[i];
                    var cpx1 = prev.x + (curr.x - prev.x) / 3;
                    var cpx2 = prev.x + (curr.x - prev.x) * 2 / 3;
                    pathD += ' C ' + cpx1 + ',' + prev.y + ' ' + cpx2 + ',' + curr.y + ' ' + curr.x + ',' + curr.y;
                }
            } else {
                // Straight lines
                pathD = 'M ' + points.map(function(p) { return p.x + ',' + p.y; }).join(' L ');
            }

            // Area fill
            if (isArea) {
                var areaPath = pathD + ' L ' + points[points.length - 1].x + ',' + chartHeight + ' L 0,' + chartHeight + ' Z';
                var area = createSVG('path', {
                    'class': 'rsl-chart-area',
                    'd': areaPath,
                    'fill': colors[seriesIndex],
                    'fill-opacity': '0.2'
                });
                if (config.animate) {
                    area.style.opacity = '0';
                    setTimeout(function() {
                        area.style.transition = 'opacity ' + config.animationDuration + 'ms ease-out';
                        area.style.opacity = '1';
                    }, 50);
                }
                g.appendChild(area);
            }

            // Line
            var line = createSVG('path', {
                'class': 'rsl-chart-line',
                'd': pathD,
                'fill': 'none',
                'stroke': colors[seriesIndex],
                'stroke-width': '3',
                'stroke-linecap': 'round',
                'stroke-linejoin': 'round'
            });

            if (config.animate) {
                var lineLength = line.getTotalLength ? line.getTotalLength() : 1000;
                line.style.strokeDasharray = lineLength;
                line.style.strokeDashoffset = lineLength;
                setTimeout(function() {
                    line.style.transition = 'stroke-dashoffset ' + config.animationDuration + 'ms ease-out';
                    line.style.strokeDashoffset = '0';
                }, 50);
            }
            g.appendChild(line);

            // Data points
            var pointsGroup = createSVG('g', { 'class': 'rsl-chart-points' });
            points.forEach(function(point, i) {
                var circle = createSVG('circle', {
                    'cx': point.x,
                    'cy': point.y,
                    'r': '5',
                    'fill': '#fff',
                    'stroke': colors[seriesIndex],
                    'stroke-width': '3',
                    'class': 'rsl-chart-point',
                    'data-value': point.value,
                    'data-label': labels[i] || '',
                    'data-series': datasets ? datasets[seriesIndex].label : ''
                });

                if (config.animate) {
                    circle.style.opacity = '0';
                    circle.style.transform = 'scale(0)';
                    circle.style.transformOrigin = point.x + 'px ' + point.y + 'px';
                    setTimeout(function() {
                        circle.style.transition = 'opacity 300ms, transform 300ms';
                        circle.style.opacity = '1';
                        circle.style.transform = 'scale(1)';
                    }, config.animationDuration + i * 50);
                }

                pointsGroup.appendChild(circle);
            });
            g.appendChild(pointsGroup);
        });

        // X-axis labels
        if (config.showAxis && labels.length > 0) {
            var xLabelsGroup = createSVG('g', { 'class': 'rsl-chart-x-labels' });

            // Calculate how many labels to skip to avoid overlap
            // Aim for max ~10 labels on x-axis
            var maxLabels = 10;
            var labelStep = labels.length > maxLabels ? Math.ceil(labels.length / maxLabels) : 1;

            labels.forEach(function(label, i) {
                // Only show every Nth label to prevent overlap
                if (i % labelStep !== 0 && i !== labels.length - 1) return;

                // For single point, center the label
                var x = numPoints === 1 ? chartWidth / 2 : i * stepX;
                var text = createSVG('text', {
                    'x': x,
                    'y': chartHeight + 20,
                    'text-anchor': 'middle',
                    'class': 'rsl-chart-axis-label',
                    'fill': 'var(--rsl-chart-text-color, #6b7280)',
                    'font-size': '12'
                });
                text.textContent = label;
                xLabelsGroup.appendChild(text);
            });
            g.appendChild(xLabelsGroup);
        }

        // Title
        if (config.title && !config.sparkline) {
            var titleText = createSVG('text', {
                'x': width / 2,
                'y': 20,
                'text-anchor': 'middle',
                'class': 'rsl-chart-title',
                'fill': 'var(--rsl-chart-text-color, #111827)',
                'font-size': '16',
                'font-weight': '600'
            });
            titleText.textContent = config.title;
            svg.appendChild(titleText);
        }

        container.appendChild(svg);

        // Add tooltip functionality
        if (config.showTooltip) {
            addTooltipListeners(container, g.querySelectorAll('.rsl-chart-point'));
        }

        // Accessibility: Add hidden data table for screen readers
        var flatValues = values.flat();
        createAccessibleTable(container, config, flatValues, labels);

        // Note: SVG is NOT aria-hidden because it contains focusable elements for keyboard nav
        // The accessible table provides redundant data access for screen readers

        // Set up keyboard navigation on data points
        var points = g.querySelectorAll('.rsl-chart-point');
        setupKeyboardNavigation(container, points, config, flatValues, labels);

        return svg;
    }

    /**
     * Render Pie/Doughnut Chart
     */
    function renderPieChart(container, config, isDoughnut) {
        var values = config.values || [];
        var labels = config.labels || [];

        var width = config.width || container.offsetWidth || 400;
        var height = config.height;
        var cx = width / 2;
        var cy = height / 2;
        var radius = Math.min(width, height) / 2 - 40;
        var innerRadius = isDoughnut ? radius * (config.innerRadius || 0.6) : 0;

        var svg = createSVG('svg', {
            'class': 'rsl-chart-svg',
            'viewBox': '0 0 ' + width + ' ' + height,
            'preserveAspectRatio': 'xMidYMid meet',
            'role': 'img',
            'aria-label': config.title || (isDoughnut ? 'Doughnut Chart' : 'Pie Chart')
        });

        if (config.title) {
            var titleEl = createSVG('title');
            titleEl.textContent = config.title;
            svg.appendChild(titleEl);
        }

        var g = createSVG('g', { 'transform': 'translate(' + cx + ',' + cy + ')' });
        svg.appendChild(g);

        var total = values.reduce(function(sum, v) { return sum + v; }, 0);
        var colors = getColors(config, values.length);

        var startAngle = -Math.PI / 2;

        values.forEach(function(value, i) {
            var angle = (value / total) * Math.PI * 2;
            var endAngle = startAngle + angle;

            // Calculate arc path
            var x1 = Math.cos(startAngle) * radius;
            var y1 = Math.sin(startAngle) * radius;
            var x2 = Math.cos(endAngle) * radius;
            var y2 = Math.sin(endAngle) * radius;

            var largeArc = angle > Math.PI ? 1 : 0;

            var d;
            if (innerRadius > 0) {
                // Doughnut
                var ix1 = Math.cos(startAngle) * innerRadius;
                var iy1 = Math.sin(startAngle) * innerRadius;
                var ix2 = Math.cos(endAngle) * innerRadius;
                var iy2 = Math.sin(endAngle) * innerRadius;

                d = 'M ' + ix1 + ',' + iy1 +
                    ' L ' + x1 + ',' + y1 +
                    ' A ' + radius + ',' + radius + ' 0 ' + largeArc + ' 1 ' + x2 + ',' + y2 +
                    ' L ' + ix2 + ',' + iy2 +
                    ' A ' + innerRadius + ',' + innerRadius + ' 0 ' + largeArc + ' 0 ' + ix1 + ',' + iy1 +
                    ' Z';
            } else {
                // Pie
                d = 'M 0,0' +
                    ' L ' + x1 + ',' + y1 +
                    ' A ' + radius + ',' + radius + ' 0 ' + largeArc + ' 1 ' + x2 + ',' + y2 +
                    ' Z';
            }

            var slice = createSVG('path', {
                'class': 'rsl-chart-slice',
                'd': d,
                'fill': colors[i],
                'stroke': 'var(--rsl-chart-bg, #fff)',
                'stroke-width': '2',
                'data-value': value,
                'data-label': labels[i] || '',
                'data-percentage': ((value / total) * 100).toFixed(1) + '%'
            });

            if (config.animate) {
                slice.style.opacity = '0';
                slice.style.transform = 'scale(0.8)';
                setTimeout(function() {
                    slice.style.transition = 'opacity ' + config.animationDuration + 'ms ease-out, transform ' + config.animationDuration + 'ms ease-out';
                    slice.style.opacity = '1';
                    slice.style.transform = 'scale(1)';
                }, 50 + i * 100);
            }

            g.appendChild(slice);

            // Value labels
            if (config.showValues) {
                var midAngle = startAngle + angle / 2;
                var labelRadius = innerRadius > 0 ? (radius + innerRadius) / 2 : radius * 0.65;
                var lx = Math.cos(midAngle) * labelRadius;
                var ly = Math.sin(midAngle) * labelRadius;

                var label = createSVG('text', {
                    'x': lx,
                    'y': ly + 4,
                    'text-anchor': 'middle',
                    'class': 'rsl-chart-slice-label',
                    'fill': '#fff',
                    'font-size': '12',
                    'font-weight': '600'
                });
                label.textContent = ((value / total) * 100).toFixed(0) + '%';

                if (config.animate) {
                    label.style.opacity = '0';
                    setTimeout(function() {
                        label.style.transition = 'opacity 300ms';
                        label.style.opacity = '1';
                    }, config.animationDuration + i * 100);
                }
                g.appendChild(label);
            }

            startAngle = endAngle;
        });

        // Center text for doughnut
        if (isDoughnut && config.title) {
            var centerText = createSVG('text', {
                'x': 0,
                'y': 0,
                'text-anchor': 'middle',
                'dominant-baseline': 'middle',
                'class': 'rsl-chart-center-text',
                'fill': 'var(--rsl-chart-text-color, #111827)',
                'font-size': '18',
                'font-weight': '600'
            });
            centerText.textContent = config.title;
            g.appendChild(centerText);
        } else if (config.title && !isDoughnut) {
            var titleText = createSVG('text', {
                'x': width / 2,
                'y': 25,
                'text-anchor': 'middle',
                'class': 'rsl-chart-title',
                'fill': 'var(--rsl-chart-text-color, #111827)',
                'font-size': '16',
                'font-weight': '600'
            });
            titleText.textContent = config.title;
            svg.appendChild(titleText);
        }

        container.appendChild(svg);

        // Add tooltip functionality
        if (config.showTooltip) {
            addTooltipListeners(container, g.querySelectorAll('.rsl-chart-slice'));
        }

        // Add legend
        if (config.showLegend) {
            renderLegend(container, labels, colors, config);
        }

        // Accessibility: Add hidden data table for screen readers
        createAccessibleTable(container, config, values, labels);

        // Note: SVG is NOT aria-hidden because it contains focusable elements for keyboard nav
        // The accessible table provides redundant data access for screen readers

        // Set up keyboard navigation on slices
        var slices = g.querySelectorAll('.rsl-chart-slice');
        setupKeyboardNavigation(container, slices, config, values, labels);

        return svg;
    }

    /**
     * Render Sparkline
     */
    function renderSparkline(container, config) {
        var values = config.values || [];
        var type = config.type === 'sparkline-bar' ? 'bar' : 'line';

        // Size mapping
        var sizeMap = {
            'xs': { width: 60, height: 20 },
            'sm': { width: 100, height: 30 },
            'md': { width: 150, height: 40 },
            'lg': { width: 200, height: 50 }
        };
        var size = sizeMap[config.size] || sizeMap.md;

        // For sparklines, only use explicit width/height if set, otherwise use size map
        // Check if height was explicitly set (not default 300)
        var explicitHeight = container.getAttribute('data-height');
        var explicitWidth = container.getAttribute('data-width');

        var width = explicitWidth ? parseInt(explicitWidth, 10) : size.width;
        var height = explicitHeight ? parseInt(explicitHeight, 10) : size.height;
        var padding = 2;

        var chartWidth = width - padding * 2;
        var chartHeight = height - padding * 2;

        var min = Math.min.apply(null, values);
        var max = Math.max.apply(null, values);
        var range = max - min || 1;

        var svg = createSVG('svg', {
            'class': 'rsl-chart-sparkline-svg',
            'viewBox': '0 0 ' + width + ' ' + height,
            'preserveAspectRatio': 'xMidYMid meet',
            'role': 'img',
            'aria-label': 'Sparkline chart'
        });

        var g = createSVG('g', { 'transform': 'translate(' + padding + ',' + padding + ')' });
        svg.appendChild(g);

        var colors = getColors(config, 1);
        var color = colors[0];

        if (type === 'bar') {
            var barWidth = chartWidth / values.length - 1;
            values.forEach(function(value, i) {
                var barHeight = ((value - min) / range) * chartHeight;
                var x = i * (barWidth + 1);
                var y = chartHeight - barHeight;

                var bar = createSVG('rect', {
                    'x': x,
                    'y': y,
                    'width': barWidth,
                    'height': barHeight,
                    'fill': color,
                    'rx': '1'
                });
                g.appendChild(bar);
            });
        } else {
            var stepX = chartWidth / (values.length - 1 || 1);
            var points = values.map(function(value, i) {
                var x = i * stepX;
                var y = chartHeight - ((value - min) / range) * chartHeight;
                return x + ',' + y;
            });

            // Area fill
            var areaPath = 'M 0,' + chartHeight + ' L ' + points.join(' L ') + ' L ' + chartWidth + ',' + chartHeight + ' Z';
            var area = createSVG('path', {
                'd': areaPath,
                'fill': color,
                'fill-opacity': '0.1'
            });
            g.appendChild(area);

            // Line
            var line = createSVG('polyline', {
                'points': points.join(' '),
                'fill': 'none',
                'stroke': color,
                'stroke-width': '2',
                'stroke-linecap': 'round',
                'stroke-linejoin': 'round'
            });
            g.appendChild(line);

            // End point
            var lastX = (values.length - 1) * stepX;
            var lastY = chartHeight - ((values[values.length - 1] - min) / range) * chartHeight;
            var dot = createSVG('circle', {
                'cx': lastX,
                'cy': lastY,
                'r': '3',
                'fill': color
            });
            g.appendChild(dot);
        }

        container.appendChild(svg);

        // Accessibility: Enhanced sparkline support
        var trend = values[values.length - 1] > values[0] ? 'increasing' : (values[values.length - 1] < values[0] ? 'decreasing' : 'stable');
        var trendChange = Math.abs(values[values.length - 1] - values[0]);
        var avg = values.reduce(function(a, b) { return a + b; }, 0) / values.length;

        // Try to get label from parent context (e.g., sparkline-label sibling)
        var contextLabel = '';
        var parentItem = container.closest('.sparkline-item');
        if (parentItem) {
            var labelEl = parentItem.querySelector('.sparkline-label');
            if (labelEl) {
                contextLabel = labelEl.textContent.trim() + ': ';
            }
        }

        var summaryText = contextLabel + 'Sparkline chart with ' + values.length + ' data points. ' +
            'Range: ' + formatNumber(min) + ' to ' + formatNumber(max) + '. ' +
            'Average: ' + formatNumber(avg) + '. ' +
            'Current value: ' + formatNumber(values[values.length - 1]) + '. ' +
            'Trend: ' + trend + ' by ' + formatNumber(trendChange) + '.';

        // Make SVG presentational (description is on container)
        svg.setAttribute('aria-hidden', 'true');

        // Make container focusable and accessible
        container.setAttribute('tabindex', '0');
        container.setAttribute('role', 'img');
        container.setAttribute('aria-label', summaryText);

        // Add hidden data table for full data access
        var table = document.createElement('table');
        table.className = 'rsl-chart-sr-only rsl-chart-data-table';
        table.setAttribute('role', 'table');

        var caption = document.createElement('caption');
        caption.textContent = contextLabel + 'Sparkline data';
        table.appendChild(caption);

        var thead = document.createElement('thead');
        var headerRow = document.createElement('tr');
        var thIndex = document.createElement('th');
        thIndex.setAttribute('scope', 'col');
        thIndex.textContent = 'Point';
        headerRow.appendChild(thIndex);
        var thValue = document.createElement('th');
        thValue.setAttribute('scope', 'col');
        thValue.textContent = 'Value';
        headerRow.appendChild(thValue);
        thead.appendChild(headerRow);
        table.appendChild(thead);

        var tbody = document.createElement('tbody');
        values.forEach(function(value, i) {
            var row = document.createElement('tr');
            var tdIndex = document.createElement('td');
            tdIndex.textContent = (i + 1);
            row.appendChild(tdIndex);
            var tdValue = document.createElement('td');
            tdValue.textContent = formatNumber(value);
            row.appendChild(tdValue);
            tbody.appendChild(row);
        });
        table.appendChild(tbody);

        container.insertBefore(table, container.firstChild);

        // Announce on focus
        container.addEventListener('focus', function() {
            announce(summaryText);
        });

        return svg;
    }

    /**
     * Render Legend
     */
    function renderLegend(container, labels, colors, config) {
        var legend = document.createElement('div');
        legend.className = 'rsl-chart-legend';
        if (config.legendPosition) {
            legend.classList.add('rsl-chart-legend-' + config.legendPosition);
        }

        labels.forEach(function(label, i) {
            var item = document.createElement('div');
            item.className = 'rsl-chart-legend-item';

            var swatch = document.createElement('span');
            swatch.className = 'rsl-chart-legend-swatch';
            swatch.style.backgroundColor = colors[i];

            var text = document.createElement('span');
            text.className = 'rsl-chart-legend-text';
            text.textContent = label;

            item.appendChild(swatch);
            item.appendChild(text);
            legend.appendChild(item);
        });

        container.appendChild(legend);
    }

    /**
     * Add tooltip event listeners
     */
    function addTooltipListeners(container, elements) {
        var tooltip = null;

        function getOrCreateTooltip() {
            if (!tooltip) {
                tooltip = document.createElement('div');
                tooltip.className = 'rsl-chart-tooltip';
                container.appendChild(tooltip);
            }
            return tooltip;
        }

        function showTooltipForElement(el) {
            var value = el.getAttribute('data-value');
            var label = el.getAttribute('data-label');
            var series = el.getAttribute('data-series');
            var percentage = el.getAttribute('data-percentage');

            var tip = getOrCreateTooltip();

            var html = '';
            if (label) html += '<div class="rsl-chart-tooltip-label">' + label + '</div>';
            if (series) html += '<div class="rsl-chart-tooltip-series">' + series + '</div>';
            html += '<div class="rsl-chart-tooltip-value">' + formatNumber(parseFloat(value));
            if (percentage) html += ' (' + percentage + ')';
            html += '</div>';

            tip.innerHTML = html;
            tip.classList.add('visible');

            return tip;
        }

        function positionTooltipAtMouse(e) {
            if (!tooltip) return;

            var rect = container.getBoundingClientRect();
            var tooltipRect = tooltip.getBoundingClientRect();

            // Center tooltip horizontally above the cursor
            var x = e.clientX - rect.left - (tooltipRect.width / 2);
            var y = e.clientY - rect.top - tooltipRect.height - 15; // 15px gap for arrow

            // Keep within container horizontally
            if (x < 5) x = 5;
            if (x + tooltipRect.width > container.offsetWidth - 5) {
                x = container.offsetWidth - tooltipRect.width - 5;
            }

            // If tooltip would go above container, show below cursor instead
            if (y < 5) {
                y = e.clientY - rect.top + 15;
                tooltip.classList.add('rsl-chart-tooltip-below');
            } else {
                tooltip.classList.remove('rsl-chart-tooltip-below');
            }

            tooltip.style.left = x + 'px';
            tooltip.style.top = y + 'px';
        }

        function positionTooltipAtElement(el) {
            if (!tooltip) return;

            var containerRect = container.getBoundingClientRect();
            var elRect = el.getBoundingClientRect();
            var tooltipRect = tooltip.getBoundingClientRect();

            // Center tooltip horizontally above the element
            var elCenterX = elRect.left + elRect.width / 2 - containerRect.left;
            var x = elCenterX - (tooltipRect.width / 2);
            var y = elRect.top - containerRect.top - tooltipRect.height - 15; // 15px gap for arrow

            // Keep within container horizontally
            if (x < 5) x = 5;
            if (x + tooltipRect.width > container.offsetWidth - 5) {
                x = container.offsetWidth - tooltipRect.width - 5;
            }

            // If tooltip would go above container, show below element instead
            if (y < 5) {
                y = elRect.bottom - containerRect.top + 15;
                tooltip.classList.add('rsl-chart-tooltip-below');
            } else {
                tooltip.classList.remove('rsl-chart-tooltip-below');
            }

            tooltip.style.left = x + 'px';
            tooltip.style.top = y + 'px';
        }

        function hideTooltip() {
            if (tooltip) {
                tooltip.classList.remove('visible');
            }
        }

        // Mouse event handlers
        function handleMouseEnter(e) {
            showTooltipForElement(e.target);
            positionTooltipAtMouse(e);
        }

        // Keyboard/focus event handlers
        function handleFocus(e) {
            showTooltipForElement(e.target);
            positionTooltipAtElement(e.target);
        }

        elements.forEach(function(el) {
            // Mouse events
            el.addEventListener('mouseenter', handleMouseEnter);
            el.addEventListener('mousemove', positionTooltipAtMouse);
            el.addEventListener('mouseleave', hideTooltip);

            // Keyboard/focus events - show tooltip when element receives focus
            el.addEventListener('focus', handleFocus);
            el.addEventListener('blur', hideTooltip);
        });
    }

    /**
     * Setup FilterBus subscription for a chart
     * @param {HTMLElement} element - Chart 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;
        }

        // Skip if already subscribed (prevents overwriting during re-render)
        if (filterSubscriptions.has(element)) {
            return;
        }

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

        // For date filtering, we need to subscribe to ALL keys (no filter)
        // so we can detect date objects from any date picker
        var subscribeOptions = {};
        if (!config.filterBusDateField && config.filterSubscribe) {
            // Only visibility filtering - restrict to specific keys
            subscribeOptions = { keys: [config.filterSubscribe] };
        }
        // 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 - Chart element
     * @param {Object} config - Configuration object (from initial setup, may be stale)
     * @param {Object} state - Current filter state
     * @param {Object} meta - Change metadata
     */
    function handleFilterBusChange(element, config, state, meta) {
        if (!state) return;

        // Prevent re-entry during chart update - check FIRST before anything else
        if (updatingCharts.has(element)) {
            return;
        }

        // Get current config from instance (may have been updated)
        var currentConfig = config;
        if (instances.has(element)) {
            currentConfig = instances.get(element).config;
        }

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

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

    /**
     * Handle date range filtering - re-render chart with filtered data
     * @param {HTMLElement} element - Chart element
     * @param {Object} config - Configuration object
     * @param {Object} state - Current filter state
     * @param {Object} meta - Change metadata
     */
    function handleDateRangeFilter(element, config, state, meta) {
        // Note: Re-entry check is done in handleFilterBusChange before this is called

        var dateField = config.filterBusDateField;
        var timeSeriesData = config.timeSeriesData;

        if (!Array.isArray(timeSeriesData) || timeSeriesData.length === 0) {
            return;
        }

        // Store original data if not already stored
        if (!originalTimeSeriesData.has(element)) {
            originalTimeSeriesData.set(element, timeSeriesData);
        }

        // 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;
            }
        });

        var filteredData;
        if (dateFilter && dateFilter.startDate) {
            // Filter data by date range
            filteredData = filterTimeSeriesData(timeSeriesData, dateField, dateFilter);
        } else {
            // No date filter - use all data
            filteredData = extractValuesAndLabels(timeSeriesData, dateField);
        }

        // Only re-render if we have data
        if (filteredData.values.length > 0) {
            // Mark as updating to prevent re-entry
            updatingCharts.add(element);

            try {
                // Update chart with filtered data
                updateChartWithFilteredData(element, config, filteredData);
            } finally {
                // Always remove from updating set, even if error occurs
                // Use setTimeout to ensure it happens after any async rendering
                setTimeout(function() {
                    updatingCharts.delete(element);
                }, 100);
            }
        }

        // Dispatch event
        element.dispatchEvent(new CustomEvent('rsl-chart:date-filter', {
            bubbles: true,
            detail: {
                dateFilter: dateFilter,
                filteredCount: filteredData.values.length,
                totalCount: timeSeriesData.length
            }
        }));
    }

    /**
     * Update chart with filtered data (re-render)
     * @param {HTMLElement} element - Chart element
     * @param {Object} config - Configuration object
     * @param {Object} filteredData - {values, labels} arrays
     */
    function updateChartWithFilteredData(element, config, filteredData) {
        if (!instances.has(element)) {
            return;
        }

        var instance = instances.get(element);

        // Store resize observer and timeout before re-rendering
        var resizeObserver = instance._resizeObserver;
        var isResizing = instance._isResizing;

        // Store FilterBus subscription ID - we'll preserve it
        var existingSubscriptionId = filterSubscriptions.get(element);

        // Update config with filtered values
        var updatedConfig = Object.assign({}, config, {
            values: filteredData.values,
            labels: filteredData.labels
        });

        // Clear and re-render (but preserve some instance state)
        element.innerHTML = '';

        // Temporarily disable animations for filter updates
        var originalAnimate = updatedConfig.animate;
        updatedConfig.animate = false;

        // Remove from instances to allow re-creation
        instances.delete(element);

        // Update element attributes for getConfig to pick up
        element.setAttribute('data-values', JSON.stringify(filteredData.values));
        element.setAttribute('data-labels', JSON.stringify(filteredData.labels));

        // Temporarily remove filter attributes to prevent re-subscription during create()
        // Store original values to restore later
        var savedDateField = element.getAttribute('data-filterbus-date-field');
        var savedTimeSeriesAttr = element.getAttribute('data-time-series-data');

        // Remove date filter attributes to prevent setupFilterBusSubscription from re-subscribing
        // and triggering the filter handler again
        element.removeAttribute('data-filterbus-date-field');
        element.removeAttribute('data-time-series-data');

        // Re-create chart (without FilterBus subscription for date filtering)
        var newInstance = create(element);

        // Restore date filter attributes
        if (savedDateField) {
            element.setAttribute('data-filterbus-date-field', savedDateField);
        }
        if (savedTimeSeriesAttr) {
            element.setAttribute('data-time-series-data', savedTimeSeriesAttr);
        }

        // Restore the config with time series data for future filter changes
        // IMPORTANT: Use original time series data from Map, not from passed config
        // (passed config may be from a previous filtered instance)
        if (newInstance) {
            newInstance.config.filterBusDateField = config.filterBusDateField;
            // Get original data from Map (stored on first filter), fall back to config
            var origData = originalTimeSeriesData.get(element);
            newInstance.config.timeSeriesData = origData || config.timeSeriesData;
        }

        // Restore the existing subscription
        if (existingSubscriptionId) {
            filterSubscriptions.set(element, existingSubscriptionId);
        }

        // Restore animation setting
        if (newInstance && originalAnimate) {
            element.setAttribute('data-animate', 'true');
        }

        // Restore resize observer reference
        if (newInstance && resizeObserver) {
            newInstance._resizeObserver = resizeObserver;
            newInstance._isResizing = isResizing;
        }

        // Add filtered indicator class
        element.classList.add('rsl-chart-filtered');
    }

    /**
     * Handle visibility filtering (show/hide based on filter value)
     * @param {HTMLElement} element - Chart 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 chartValue = 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 (chartValue !== undefined && chartValue !== null) {
                // Check if chart's filter value matches the current filter
                // Support comma-separated values for multi-match
                var chartValues = String(chartValue).split(',').map(function(v) {
                    return v.trim().toLowerCase();
                });
                shouldShow = chartValues.includes(filterLower);
            }
            // If chart has no filterValue, it always shows (like a "Total" chart)
        }

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

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

    /**
     * Create chart instance
     */
    function create(element) {
        if (!element) return null;

        // Skip if already initialized
        if (instances.has(element)) {
            return instances.get(element);
        }

        var config = getConfig(element);

        // If time series data is provided, extract initial values and labels
        if (config.filterBusDateField && config.timeSeriesData && Array.isArray(config.timeSeriesData)) {
            var initialData = extractValuesAndLabels(config.timeSeriesData, config.filterBusDateField);
            if (initialData.values.length > 0) {
                config.values = initialData.values;
                config.labels = initialData.labels;
            }
        }

        // Skip creation if chart expects time series data but doesn't have it yet
        // (it will be set dynamically later via JavaScript)
        if (config.filterBusDateField && !config.timeSeriesData && (!config.values || config.values.length === 0)) {
            return null;
        }

        // Clear container
        element.innerHTML = '';

        // Add base class
        element.classList.add('rsl-chart');

        // Add size class
        if (config.size) {
            element.classList.add('rsl-chart-' + config.size);
        }

        // Add color scheme class
        if (config.colorScheme) {
            element.classList.add('rsl-chart-scheme-' + config.colorScheme);
        }

        // Check if sparkline type
        var isSparkline = config.sparkline || config.type.startsWith('sparkline');

        // Add sparkline class
        if (isSparkline) {
            element.classList.add('rsl-chart-sparkline');
        }

        var svg;

        // Render based on type
        switch (config.type) {
            case 'bar':
                svg = renderBarChart(element, config);
                break;
            case 'line':
                svg = renderLineChart(element, config, false);
                break;
            case 'area':
                svg = renderLineChart(element, config, true);
                break;
            case 'pie':
                svg = renderPieChart(element, config, false);
                break;
            case 'doughnut':
                svg = renderPieChart(element, config, true);
                break;
            case 'sparkline':
            case 'sparkline-line':
                config.sparkline = true;
                svg = renderSparkline(element, config);
                break;
            case 'sparkline-bar':
                config.sparkline = true;
                svg = renderSparkline(element, config);
                break;
            default:
                console.warn('RSL Charts: Unknown chart type "' + config.type + '"');
                svg = renderBarChart(element, config);
        }

        var instance = {
            element: element,
            config: config,
            svg: svg,
            _initialWidth: element.offsetWidth,
            update: function(newData) {
                updateChart(element, newData);
            },
            destroy: function() {
                destroyChart(element);
            },
            resize: function() {
                resizeChart(element);
            }
        };

        instances.set(element, instance);

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

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

        // Set up responsive resize (but not for sparklines which have fixed sizes)
        if (config.responsive && !isSparkline && !config.width) {
            // Use a flag to prevent resize during initial render
            instance._isResizing = true; // Start as true to prevent initial trigger

            var resizeObserver = new ResizeObserver(function(entries) {
                entries.forEach(function(entry) {
                    if (entry.target === element && !instance._isResizing) {
                        // Only resize if width actually changed significantly (more than 10px)
                        var newWidth = entry.contentRect.width;
                        if (Math.abs(newWidth - instance._initialWidth) > 10) {
                            instance._initialWidth = newWidth;
                            resizeChart(element);
                        }
                    }
                });
            });
            resizeObserver.observe(element);
            instance._resizeObserver = resizeObserver;

            // Allow resize observation after initial render is complete
            setTimeout(function() {
                instance._isResizing = false;
                instance._initialWidth = element.offsetWidth;
            }, 500);
        }

        return instance;
    }

    /**
     * Update chart with new data
     */
    function updateChart(element, newData) {
        if (!instances.has(element)) return;

        var instance = instances.get(element);

        // Update config
        Object.assign(instance.config, newData);

        // Re-render
        element.innerHTML = '';

        // Re-create with updated config
        instances.delete(element);

        // Apply new data as attributes
        if (newData.values) {
            element.setAttribute('data-values', JSON.stringify(newData.values));
        }
        if (newData.labels) {
            element.setAttribute('data-labels', JSON.stringify(newData.labels));
        }

        create(element);
    }

    /**
     * Resize chart
     */
    function resizeChart(element) {
        if (!instances.has(element)) return;

        var instance = instances.get(element);
        var config = instance.config;

        // Only resize if width is auto
        if (config.width) return;

        // Prevent recursive resizing
        if (instance._isResizing) return;

        // Debounce
        if (instance._resizeTimeout) {
            clearTimeout(instance._resizeTimeout);
        }

        instance._resizeTimeout = setTimeout(function() {
            instance._isResizing = true;

            // Store the resize observer before deleting instance
            var resizeObserver = instance._resizeObserver;

            element.innerHTML = '';
            instances.delete(element);

            // Temporarily disable animations for resize
            var originalAnimate = element.getAttribute('data-animate');
            element.removeAttribute('data-animate');

            create(element);

            // Restore animation attribute
            if (originalAnimate !== null) {
                element.setAttribute('data-animate', originalAnimate);
            }

            // Re-attach the resize observer reference and reset flag
            var newInstance = instances.get(element);
            if (newInstance) {
                newInstance._resizeObserver = resizeObserver;
                newInstance._isResizing = false;
            }
        }, 150);
    }

    /**
     * Destroy chart instance
     */
    function destroyChart(element) {
        if (!instances.has(element)) return;

        var instance = instances.get(element);

        // 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);

        // Clean up original time series data
        originalTimeSeriesData.delete(element);

        // Clean up resize observer
        if (instance._resizeObserver) {
            instance._resizeObserver.disconnect();
        }

        // Clear timeout
        if (instance._resizeTimeout) {
            clearTimeout(instance._resizeTimeout);
        }

        // Remove content
        element.innerHTML = '';
        element.classList.remove('rsl-chart');
        element.classList.remove('rsl-chart-initialized');
        element.classList.remove('rsl-chart-hidden');

        instances.delete(element);
    }

    /**
     * Initialize all charts on page
     */
    function init() {
        var charts = document.querySelectorAll('[data-rsl-chart]');
        charts.forEach(function(chart) {
            // Check for animate-on-view
            if (chart.hasAttribute('data-animate-on-view')) {
                setupAnimateOnView(chart);
            } else {
                create(chart);
            }
        });
    }

    /**
     * Set up IntersectionObserver for animate-on-view
     */
    function setupAnimateOnView(element) {
        var observer = new IntersectionObserver(function(entries) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    create(entry.target);
                    observer.unobserve(entry.target);
                }
            });
        }, { threshold: 0.1 });

        observer.observe(element);
    }

    /**
     * Set data programmatically
     */
    function setData(element, values, labels) {
        if (!element) return;

        if (instances.has(element)) {
            updateChart(element, { values: values, labels: labels });
        } else {
            element.setAttribute('data-values', JSON.stringify(values));
            if (labels) {
                element.setAttribute('data-labels', JSON.stringify(labels));
            }
            create(element);
        }
    }

    // Public API
    window.RSL.Charts = {
        init: init,
        create: create,
        update: updateChart,
        destroy: destroyChart,
        resize: resizeChart,
        setData: setData,
        getColorSchemes: function() { return Object.keys(COLOR_SCHEMES); },
        getColors: function(scheme, count) {
            return getColors({ colorScheme: scheme || 'primary' }, count || 8);
        }
    };

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

})();
