<?php
/**
 * Optimized Advanced Chart Renderer
 *
 * This file contains the ChartRenderer class which provides comprehensive
 * chart rendering capabilities using Chart.js library with enhanced security
 * analytics features. It supports all major Chart.js chart types including
 * specialized security visualizations.
 *
 * The class implements performance optimizations through static caching,
 * lazy asset loading, and efficient configuration management. It provides
 * specialized methods for security dashboards, real-time monitoring, and
 * hierarchical data visualizations.
 *
 * @package    UltraguardPro\view
 * @author     Ultraguard Team <support@ultraguardpro.com>
 * @copyright  2024 UltraguardPro
 * @license    GPL-2.0-or-later
 * @version    1.0.0
 * @link       https://ultraguardpro.com/docs/charts
 */

namespace UltraguardPro\view;

defined( 'ABSPATH' ) || exit;

/**
 * Optimized Advanced Chart Renderer
 *
 * Supports all Chart.js chart types with enhanced security analytics features.
 * Implements Singleton pattern with extensive static caching for optimal
 * performance. Provides specialized methods for security-focused visualizations
 * including threat dashboards and real-time monitoring.
 *
 * Key features:
 * - Support for all Chart.js chart types (line, bar, pie, doughnut, radar, etc.)
 * - Treemap charts for hierarchical data visualization
 * - Security-themed color schemes and configurations
 * - Real-time monitoring with streaming data support
 * - Comprehensive dashboard layouts with flexible grid systems
 * - Performance optimizations through static caching and lazy loading
 * - Extensive sanitization and security validation
 * - WordPress action/filter hooks for extensibility
 *
 * @package    UltraguardPro\view
 * @author     Ultraguard Team
 * @version    1.0.0
 * @since      1.0.0
 * @see        BaseRenderer
 *
 * @example
 * // Render a basic line chart
 * echo ChartRenderer::render_chart([
 *     'id' => 'threat-timeline',
 *     'chart_type' => 'line',
 *     'title' => 'Threat Activity Timeline',
 *     'labels' => ['Jan', 'Feb', 'Mar', 'Apr'],
 *     'datasets' => [[
 *         'label' => 'Threats Blocked',
 *         'data' => [12, 19, 3, 5]
 *     ]]
 * ], []);
 *
 * @example
 * // Render a security dashboard
 * echo ChartRenderer::render_security_dashboard([
 *     'title' => 'Security Overview',
 *     'columns' => 3,
 *     'charts' => [
 *         ['type' => 'chart', 'chart_type' => 'pie', 'title' => 'Threat Types'],
 *         ['type' => 'chart', 'chart_type' => 'line', 'title' => 'Timeline'],
 *         ['type' => 'chart', 'chart_type' => 'bar', 'title' => 'Severity Levels']
 *     ]
 * ], []);
 */
class ChartRenderer extends BaseRenderer {

    /**
     * Singleton instance
     *
     * Static instance variable for Singleton pattern implementation.
     * Ensures only one instance of ChartRenderer exists globally.
     *
     * @var self|null
     * @access private
     * @static
     */
    private static $instance = null;

    /**
     * Cached allowed chart types
     *
     * Pre-defined array of valid Chart.js chart types for validation.
     * Static for performance and memory efficiency.
     *
     * @var array<string>
     * @access private
     * @static
     */
    private static $allowed_chart_types = array( 'line', 'bar', 'pie', 'doughnut', 'radar', 'polarArea', 'bubble', 'scatter', 'treemap' );

    /**
     * Cached allowed layouts
     *
     * Pre-defined array of valid dashboard layout types.
     * Static for performance and memory efficiency.
     *
     * @var array<string>
     * @access private
     * @static
     */
    private static $allowed_layouts = array( 'grid', 'flex' );

    /**
     * Cached security color schemes
     *
     * Static cache for security-themed color palettes to avoid
     * redundant color generation calculations.
     *
     * @var array<string, array<string>>
     * @access private
     * @static
     */
    private static $security_color_schemes = array();

    /**
     * Static cache for sanitized configurations
     *
     * In-memory cache for rendered chart configurations and outputs
     * to avoid redundant processing for identical chart definitions.
     *
     * @var array<string, mixed>
     * @access private
     * @static
     */
    private static $config_cache = array();

    /**
     * Whether Chart.js assets have been enqueued
     *
     * Static flag to track if Chart.js core assets have been
     * enqueued for the current request.
     *
     * @var bool
     * @access private
     * @static
     */
    private static $chart_assets_enqueued = false;

    /**
     * Whether real-time assets have been enqueued
     *
     * Static flag to track if real-time monitoring assets have been
     * enqueued for the current request.
     *
     * @var bool
     * @access private
     * @static
     */
    private static $realtime_assets_enqueued = false;

    /**
     * Default chart scheme with secure defaults
     *
     * Defines default values for chart configurations with security-focused
     * defaults. Used as fallback when chart scheme values are not provided.
     *
     * @var array<string, mixed>
     * @access private
     */
    private const DEFAULT_CHART_SCHEME = array(
        'id'                  => '',
        'chart_type'          => 'line',
        'height'              => '300px',
        'title'               => '',
        'description'         => '',
        'labels'              => array(),
        'datasets'            => array(),
        'data'                => array(),
        'options'             => array(),
        'x_axis_title'        => '',
        'y_axis_title'        => '',
        'security_theme'      => '',
        'step_size'           => null,
        'logarithmic_y_axis'  => false,
    );

    /**
     * Default dashboard scheme
     *
     * Defines default values for dashboard configurations including
     * layout options and structural defaults.
     *
     * @var array<string, mixed>
     * @access private
     */
    private const DEFAULT_DASHBOARD_SCHEME = array(
        'charts'     => array(),
        'layout'     => 'grid',
        'columns'    => 2,
        'title'      => '',
        'description' => '',
    );

    /**
     * Default monitor scheme
     *
     * Defines default values for real-time monitoring configurations
     * including refresh intervals and endpoint settings.
     *
     * @var array<string, mixed>
     * @access private
     */
    private const DEFAULT_MONITOR_SCHEME = array(
        'id'              => '',
        'refresh_interval' => 5000,
        'ajax_endpoint'   => '',
        'nonce'           => '',
    );

    /**
     * Get singleton instance
     *
     * Returns the singleton instance of ChartRenderer. Creates a new
     * instance if one doesn't exist. Implements lazy initialization.
     *
     * @return self Singleton instance
     * @access private
     * @static
     * @since  1.0.0
     */
    private static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Render chart with optimized performance
     *
     * Main method for rendering Chart.js charts with comprehensive
     * configuration, validation, and performance optimizations.
     * Supports all standard Chart.js chart types.
     *
     * @param  array  $scheme   Chart configuration scheme
     * @param  array  $context  Rendering context data
     * @return string           Rendered HTML chart output
     * @access public
     * @static
     * @since  1.0.0
     *
     * @throws \InvalidArgumentException If scheme data is invalid
     *
     * @example
     * $html = ChartRenderer::render_chart([
     *     'id' => 'threat-analysis',
     *     'chart_type' => 'bar',
     *     'title' => 'Threat Analysis',
     *     'labels' => ['SQLi', 'XSS', 'RFI', 'LFI'],
     *     'datasets' => [[
     *         'label' => 'Threat Count',
     *         'data' => [45, 32, 18, 12],
     *         'backgroundColor' => '#3b82f6'
     *     ]]
     * ], []);
     */
    public static function render_chart( $scheme, $context ) {
        // Generate cache key for this chart configuration
        $cache_key = 'chart_' . md5( wp_json_encode( array( $scheme, $context ) ) );

        // Check static cache first
        if ( isset( self::$config_cache[ $cache_key ] ) ) {
            return self::$config_cache[ $cache_key ];
        }

        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        // Merge with defaults once
        $scheme = wp_parse_args( $scheme, self::DEFAULT_CHART_SCHEME );

        // Ensure ID is set
        if ( empty( $scheme['id'] ) ) {
            $scheme['id'] = 'chart-' . uniqid();
        }

        // Sanitize and validate inputs
        $chart_id   = sanitize_html_class( $scheme['id'] );
        $chart_type = sanitize_key( $scheme['chart_type'] );
        $height     = esc_attr( $scheme['height'] );

        // Validate chart type against cached allowed types
        if ( ! in_array( $chart_type, self::$allowed_chart_types, true ) ) {
            $chart_type = 'line';
            $scheme['chart_type'] = 'line';
        }

        // Build chart configuration
        $chart_config = $instance->build_chart_config( $scheme, $context );

        // Generate title and description HTML only if needed
        $title_html = '';
        if ( ! empty( $scheme['title'] ) ) {
            $title_html = '<h4 class="ultraguard-chart-title">' . esc_html( is_array( $scheme['title'] ) ? (string)( $scheme['title']['text'] ?? $scheme['title']['content'] ?? wp_json_encode( $scheme['title'] ) ) : (string)( $scheme['title'] ) ) . '</h4>';
        }

        $description_html = '';
        if ( ! empty( $scheme['description'] ) ) {
            $description_html = '<p class="ultraguard-chart-description">' . esc_html( is_array( $scheme['description'] ) ? (string)( $scheme['description']['text'] ?? $scheme['description']['content'] ?? wp_json_encode( $scheme['description'] ) ) : (string)( $scheme['description'] ) ) . '</p>';
        }

        // JSON encode with flags for performance
        $json_config   = wp_json_encode( $chart_config, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT );
        $escaped_config = $json_config ?: '{}';

        $canvas_style = 'height: ' . $height . '; max-width: 100%;';

        // Detect empty / all-zero chart data and show a gathering overlay instead of a blank chart
        $is_empty_chart = false;
        $labels   = $scheme['labels']   ?? array();
        $datasets = $scheme['datasets'] ?? array();
        if ( empty( $labels ) && empty( $datasets ) ) {
            $is_empty_chart = true;
        } elseif ( ! empty( $datasets ) ) {
            $all_zero = true;
            foreach ( $datasets as $ds ) {
                $ds_data = is_array( $ds ) ? ( $ds['data'] ?? array() ) : array();
                foreach ( $ds_data as $v ) {
                    if ( ! empty( $v ) ) {
                        $all_zero = false;
                        break 2;
                    }
                }
            }
            if ( $all_zero && empty( array_filter( $labels ) ) ) {
                $is_empty_chart = true;
            }
        }

        if ( $is_empty_chart ) {
            $gathering_msg  = __( 'Gathering data…', 'ultraguard-security-suite' );
            $gathering_sub  = __( 'Chart data is being collected. Check back shortly.', 'ultraguard-security-suite' );
            $content = $title_html . $description_html .
                '<div class="ultraguard-chart-container ultraguard-chart-empty" style="height:' . $height . '; position:relative; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,0.03); border-radius:8px; border:1px dashed #cbd5e1;">' .
                '<div class="ultraguard-chart-gathering" style="text-align:center; padding:24px; color:#64748b;">' .
                '<span class="dashicons dashicons-update" style="font-size:28px; width:28px; height:28px; animation: ultraguard-spin 1.5s linear infinite; display:block; margin:0 auto 12px;"></span>' .
                '<p style="font-size:14px; font-weight:600; margin:0 0 4px;">' . esc_html( $gathering_msg ) . '</p>' .
                '<p style="font-size:12px; margin:0;">' . esc_html( $gathering_sub ) . '</p>' .
                '</div></div>';
        } else {
            $content = $title_html . $description_html .
                '<div class="ultraguard-chart-container">' .
                '<canvas id="' . esc_attr( $chart_id ) . '" style="' . $canvas_style . '" data-config=\'' . $escaped_config . '\'></canvas>' .
                '</div>';
        }

        $output = $instance->render_wrapper( $content, $scheme, $context );
        $output = $instance->after_render( $output, $scheme, $context );

        // Lazy enqueue assets only when needed
        $instance->enqueue_chart_assets( $chart_type );

        // Defer action hook to shutdown for better performance
        if ( has_action( 'ultraguard_chart_rendered' ) ) {
            add_action( 'shutdown', function() use ( $chart_id, $chart_type, $scheme ) {
                do_action( 'ultraguard_chart_rendered', $chart_id, $chart_type, $scheme );
            }, 20 );
        }

        // Cache the output
        self::$config_cache[ $cache_key ] = $output;

        return $output;
    }

    /**
     * Render Treemap Chart (for hierarchical data visualization)
     *
     * Specialized method for rendering treemap charts which visualize
     * hierarchical data structures. Useful for threat categorization
     * and security event classification.
     *
     * @param  array  $scheme   Treemap configuration scheme
     * @param  array  $context  Rendering context data
     * @return string           Rendered HTML treemap output
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * $html = ChartRenderer::render_treemap([
     *     'id' => 'threat-categories',
     *     'title' => 'Threat Category Distribution',
     *     'datasets' => [[
     *         'tree' => [
     *             ['category' => 'Web Attacks', 'value' => 40],
     *             ['category' => 'Malware', 'value' => 30],
     *             ['category' => 'Insider Threats', 'value' => 20],
     *             ['category' => 'DDoS', 'value' => 10]
     *         ]
     *     ]]
     * ], []);
     */
    public static function render_treemap( $scheme, $context ) {
        $scheme['chart_type'] = 'treemap';
        return self::render_chart( $scheme, $context );
    }

    /**
     * Build comprehensive chart configuration with caching
     *
     * Constructs complete Chart.js configuration object from scheme data
     * with extensive sanitization and performance caching. Handles both
     * standard charts and specialized treemap configurations.
     *
     * @param  array  $scheme   Chart scheme data
     * @param  array  $context  Rendering context data
     * @return array            Complete Chart.js configuration
     * @access protected
     * @since  1.0.0
     *
     * @see    build_treemap_config()
     * @see    sanitize_datasets()
     * @see    get_default_options()
     */
    protected function build_chart_config( $scheme, $context ) {
        $config_key = 'config_' . md5( wp_json_encode( array( $scheme, $context ) ) );

        if ( isset( self::$config_cache[ $config_key ] ) ) {
            return self::$config_cache[ $config_key ];
        }

        // Build base configuration
        if ( 'treemap' === $scheme['chart_type'] ) {
            $default_config = $this->build_treemap_config( $scheme );
        } else {
            $default_config = array(
                'type' => sanitize_key( $scheme['chart_type'] ),
                'data' => array(
                    'labels'   => $this->sanitize_array_values( $scheme['labels'] ),
                    'datasets' => $this->sanitize_datasets( $scheme['datasets'] ),
                ),
                'options' => $this->get_default_options( $scheme['chart_type'], $scheme ),
            );

            // Handle root-level data if no datasets provided
            if ( ! empty( $scheme['data'] ) && is_array( $scheme['data'] ) && empty( $scheme['datasets'] ) ) {
                $default_config['data'] = array_merge(
                    $default_config['data'],
                    $this->sanitize_chart_data( $scheme['data'] )
                );
            }
        }

        // Merge custom options if provided
        if ( ! empty( $scheme['options'] ) && is_array( $scheme['options'] ) ) {
            $default_config['options'] = $this->deep_merge_chart_options(
                $default_config['options'],
                $this->sanitize_chart_options( $scheme['options'] )
            );
        }

        // Apply security analytics configurations
//        $default_config = $this->apply_security_analytics_config( $default_config, $scheme );

        // Apply filters and cache result
        $final_config = apply_filters( 'ultraguard_chart_config', $default_config, $scheme, $context );
        self::$config_cache[ $config_key ] = $final_config;

        return $final_config;
    }

    /**
     * Build treemap-specific configuration
     *
     * Constructs specialized configuration for treemap charts which
     * require different data structure than standard Chart.js charts.
     *
     * @param  array  $scheme  Treemap scheme data
     * @return array           Treemap Chart.js configuration
     * @access protected
     * @since  1.0.0
     *
     * @see    sanitize_treemap_datasets()
     */
    protected function build_treemap_config( $scheme ) {
        return array(
            'type' => 'treemap',
            'data' => array(
                'datasets' => $this->sanitize_treemap_datasets( $scheme['datasets'] ),
            ),
            'options' => $this->get_default_options( 'treemap', $scheme ),
        );
    }

    /**
     * Sanitize treemap datasets
     *
     * Validates and sanitizes treemap-specific dataset structures
     * including hierarchical tree data.
     *
     * @param  array  $datasets  Raw treemap datasets
     * @return array             Sanitized treemap datasets
     * @access protected
     * @since  1.0.0
     *
     * @see    sanitize_treemap_tree()
     */
    protected function sanitize_treemap_datasets( $datasets ) {
        if ( ! is_array( $datasets ) ) {
            return array();
        }

        $sanitized = array();
        foreach ( $datasets as $dataset ) {
            if ( ! is_array( $dataset ) ) {
                continue;
            }

            $sanitized_dataset = array();
            foreach ( $dataset as $key => $value ) {
                if ( 'tree' === $key && is_array( $value ) ) {
                    $sanitized_dataset[ $key ] = $this->sanitize_treemap_tree( $value );
                } elseif ( is_string( $value ) ) {
                    $sanitized_dataset[ $key ] = esc_html( $value );
                } elseif ( is_array( $value ) ) {
                    $sanitized_dataset[ $key ] = $this->sanitize_array_values( $value );
                } else {
                    $sanitized_dataset[ $key ] = $value;
                }
            }
            $sanitized[] = $sanitized_dataset;
        }

        return $sanitized;
    }

    /**
     * Sanitize treemap tree structure
     *
     * Validates and sanitizes hierarchical tree data structure
     * for treemap charts.
     *
     * @param  array  $tree  Raw tree structure data
     * @return array         Sanitized tree structure
     * @access protected
     * @since  1.0.0
     */
    protected function sanitize_treemap_tree( $tree ) {
        if ( ! is_array( $tree ) ) {
            return array();
        }

        $sanitized = array();
        foreach ( $tree as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }

            $sanitized_item = array();
            foreach ( $item as $key => $value ) {
                $sanitized_item[ $key ] = is_string( $value ) ? esc_html( $value ) : $value;
            }
            $sanitized[] = $sanitized_item;
        }

        return $sanitized;
    }

    /**
     * Get default options for each chart type with caching
     *
     * Returns pre-configured Chart.js options optimized for each chart type
     * with security analytics enhancements. Implements static caching for
     * performance.
     *
     * @param  string  $chart_type  Chart type identifier
     * @param  array   $scheme      Chart scheme data
     * @return array                Chart.js options configuration
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $options = $this->get_default_options('line', $scheme);
     * // Returns comprehensive line chart options with security optimizations
     */
    protected function get_default_options( $chart_type, $scheme ) {
        static $options_cache = array();

        $cache_key = $chart_type . '_' . md5( wp_json_encode( $scheme ) );

        if ( isset( $options_cache[ $cache_key ] ) ) {
            return $options_cache[ $cache_key ];
        }

        $use_log_scale         = ! empty( $scheme['logarithmic_y_axis'] );
        $y_axis_type           = $use_log_scale ? 'logarithmic' : 'linear';

        $common_options = array(
            'responsive' => true,
            'maintainAspectRatio' => false,
            'plugins' => array(
                'legend' => array(
                    'position' => 'top',
                    'labels' => array(
                        'usePointStyle' => true,
                        'padding' => 20,
                    ),
                ),
                'tooltip' => array(
                    'mode' => 'index',
                    'intersect' => false,
                ),
            ),
            'interaction' => array(
                'mode' => 'nearest',
                'axis' => 'x',
                'intersect' => false,
            ),
        );

        $type_specific_options = array();

        switch ( $chart_type ) {
            case 'line':
            case 'bar':
                $type_specific_options = array(
                    'scales' => array(
                        'x' => array(
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['x_axis_title'] ),
                                'text' => esc_html( $scheme['x_axis_title'] ),
                            ),
                            'grid' => array(
                                'drawOnChartArea' => ( 'line' === $chart_type ? false : true ),
                            ),
                        ),
                        'y' => array(
                            'type' => $y_axis_type,
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['y_axis_title'] ),
                                'text' => esc_html( $scheme['y_axis_title'] ),
                            ),
                            'beginAtZero' => ! $use_log_scale,
                        ),
                    ),
                );
                break;

            case 'pie':
            case 'doughnut':
                $type_specific_options = array(
                    'cutout' => 'doughnut' === $chart_type ? '50%' : 0,
                );
                break;

            case 'radar':
                $type_specific_options = array(
                    'scales' => array(
                        'r' => array(
                            'beginAtZero' => true,
                            'ticks' => array(
                                'stepSize' => is_numeric( $scheme['step_size'] ) ? floatval( $scheme['step_size'] ) : null,
                            ),
                        ),
                    ),
                );
                break;

            case 'polarArea':
                $type_specific_options = array(
                    'scales' => array(
                        'r' => array(
                            'beginAtZero' => true,
                        ),
                    ),
                );
                break;

            case 'bubble':
                $type_specific_options = array(
                    'scales' => array(
                        'x' => array(
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['x_axis_title'] ),
                                'text' => esc_html( $scheme['x_axis_title'] ),
                            ),
                        ),
                        'y' => array(
                            'type' => $y_axis_type,
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['y_axis_title'] ),
                                'text' => esc_html( $scheme['y_axis_title'] ),
                            ),
                            'beginAtZero' => ! $use_log_scale,
                        ),
                    ),
                );
                break;

            case 'scatter':
                $type_specific_options = array(
                    'scales' => array(
                        'x' => array(
                            'type' => 'linear',
                            'position' => 'bottom',
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['x_axis_title'] ),
                                'text' => esc_html( $scheme['x_axis_title'] ),
                            ),
                        ),
                        'y' => array(
                            'type' => $y_axis_type,
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['y_axis_title'] ),
                                'text' => esc_html( $scheme['y_axis_title'] ),
                            ),
                            'beginAtZero' => ! $use_log_scale,
                        ),
                    ),
                );
                break;

            case 'treemap':
                $type_specific_options = array(
                    'plugins' => array(
                        'legend' => array(
                            'display' => false,
                        )
                    )
                );
                break;

            default:
                $type_specific_options = array(
                    'scales' => array(
                        'x' => array(
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['x_axis_title'] ),
                                'text' => esc_html( $scheme['x_axis_title'] ),
                            ),
                        ),
                        'y' => array(
                            'display' => true,
                            'title' => array(
                                'display' => ! empty( $scheme['y_axis_title'] ),
                                'text' => esc_html( $scheme['y_axis_title'] ),
                            ),
                            'beginAtZero' => true,
                        ),
                    ),
                );
        }

        // Efficient array merging
        $options = $this->array_merge_recursive_distinct( $common_options, $type_specific_options );

        // Apply filters and cache
        $final_options = apply_filters( 'ultraguard_chart_options', $options, $chart_type, $scheme );
        $options_cache[ $cache_key ] = $final_options;

        return $final_options;
    }

    /**
     * Enqueue chart assets with lazy loading
     *
     * Dynamically loads Chart.js and related plugin assets only when needed.
     * Implements lazy loading to minimize page load impact.
     *
     * @param  string  $chart_type  The type of chart being rendered
     * @return void
     * @access protected
     * @since  1.0.0
     *
     * @see    output_chart_init_script()
     */
    /** Whether the gathering-overlay CSS has already been injected */
    private static $gathering_css_injected = false;

    protected function enqueue_chart_assets( $chart_type = 'line' ) {
        // Inject spin-animation CSS once for the gathering overlay
        if ( ! self::$gathering_css_injected ) {
            self::$gathering_css_injected = true;
            add_action( 'admin_head', function() {
                echo '<style id="ultraguard-gathering-anim">'
                    . '@keyframes ultraguard-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}'
                    . '.ultraguard-chart-gathering .dashicons,'
                    . '.ultraguard-gathering-empty .dashicons,'
                    . '.ultraguard-spin-icon{'
                    . 'animation:ultraguard-spin 1.5s linear infinite;}'
                    . '.ultraguard-gathering-empty{'
                    . 'border:1px dashed #e2e8f0;border-radius:8px;background:rgba(0,0,0,.02);}'
                    . '</style>';
            }, 99 );
        }
        // Return early if already enqueued for this request
        if ( self::$chart_assets_enqueued ) {
            return;
        }

        // Register Chart.js only once
        if ( ! wp_script_is( 'chart-js', 'registered' ) ) {
            wp_register_script(
                'chart-js',
                ULTRAGUARD_PRO_URL . 'assets/js/chart.min.js',
                array(),
                '3.9.1',
                true
            );
        }

        wp_enqueue_script( 'chart-js' );

        // Load specialized plugins based on chart type
        if ( 'treemap' === $chart_type && ! wp_script_is( 'chart-js-treemap', 'registered' ) ) {
            wp_register_script(
                'chart-js-treemap',
                ULTRAGUARD_PRO_URL . 'assets/js/chartjs-chart-treemap.min.js',
                array( 'chart-js' ),
                '2.0.0',
                true
            );
            wp_enqueue_script( 'chart-js-treemap' );
        }

        // Add inline script only once
        if ( ! has_action( 'wp_footer', array( $this, 'output_chart_init_script' ) ) ) {
            add_action( 'wp_footer', array( $this, 'output_chart_init_script' ), 99 );
        }

        // Mark as enqueued
        self::$chart_assets_enqueued = true;

        // Defer action hook
        if ( has_action( 'ultraguard_chart_assets_enqueued' ) ) {
            add_action( 'shutdown', function() {
                do_action( 'ultraguard_chart_assets_enqueued' );
            }, 20 );
        }
    }

    /**
     * Output chart initialization script in footer
     *
     * Outputs JavaScript to initialize all Chart.js instances on the page.
     * Includes error handling and dynamic content support.
     *
     * @return void
     * @access public
     * @since  1.0.0
     *
     * @see    enqueue_chart_assets()
     */
    public function output_chart_init_script() {
        if ( ! wp_script_is( 'chart-js', 'enqueued' ) ) {
            return;
        }?>
        <script>
        (function($) {
            'use strict';

            var ultraguardChartInitialized = false;

            function initializeCharts() {
                if (ultraguardChartInitialized || typeof Chart === 'undefined') {
                    return;
                }

                $('.ultraguard-chart-container canvas').each(function() {
                    var canvas = this;
                    var configData = $(canvas).data('config');

                    if (!canvas || !configData) {
                        return;
                    }

                    var config = typeof configData === 'string' ?
                        JSON.parse(configData) : configData;

                    var existingChart = Chart.getChart(canvas);
                    if (existingChart) {
                        existingChart.destroy();
                    }

                    try {
                        new Chart(canvas, config);
                    } catch (error) {
                        console.error('Chart initialization error:', error);
                    }
                });

                ultraguardChartInitialized = true;
            }

            // Initialize on DOM ready
            $(function() {
                if (typeof Chart !== 'undefined') {
                    initializeCharts();
                } else {
                    $(document).on('chartjs:loaded', initializeCharts);
                }
            });

            // Reinitialize on dynamic content changes
            $(document).on('ultraguard:dynamic:content:loaded', function() {
                setTimeout(initializeCharts, 100);
            });

        })(jQuery);
        </script>
        <?php
    }

    /**
     * Enqueue real-time monitoring assets
     *
     * Loads Chart.js streaming plugin for real-time monitoring capabilities.
     * Ensures dependencies are loaded in correct order.
     *
     * @return void
     * @access protected
     * @since  1.0.0
     *
     * @see    enqueue_chart_assets()
     */
    protected function enqueue_realtime_assets() {
        // Return early if already enqueued
        if ( self::$realtime_assets_enqueued ) {
            return;
        }

        // Ensure Chart.js is loaded
        $this->enqueue_chart_assets( 'line' );

        // Register streaming plugin only once
        if ( ! wp_script_is( 'chart-js-plugin-streaming', 'registered' ) ) {
            wp_register_script(
                'chart-js-plugin-streaming',
                ULTRAGUARD_PRO_URL . 'assets/js/chartjs-plugin-streaming.min.js',
                array( 'chart-js' ),
                '2.0.0',
                true
            );
        }

        wp_enqueue_script( 'chart-js-plugin-streaming' );

        // Mark as enqueued
        self::$realtime_assets_enqueued = true;

        // Defer action hook
        if ( has_action( 'ultraguard_realtime_assets_enqueued' ) ) {
            add_action( 'shutdown', function() {
                do_action( 'ultraguard_realtime_assets_enqueued' );
            }, 20 );
        }
    }

    /**
     * Sanitize array values for chart labels
     *
     * Sanitizes array values using esc_html() for safe output in chart labels.
     * Returns empty array for invalid input.
     *
     * @param  array  $values  Array values to sanitize
     * @return array           Sanitized values array
     * @access protected
     * @since  1.0.0
     */
    protected function sanitize_array_values( $values ) {
        if ( ! is_array( $values ) ) {
            return array();
        }

        return array_map( 'esc_html', $values );
    }

    /**
     * Sanitize chart datasets
     *
     * Validates and sanitizes chart dataset structures including nested
     * data arrays. Handles different data structures for various chart types.
     *
     * @param  array  $datasets  Raw datasets array
     * @return array             Sanitized datasets array
     * @access protected
     * @since  1.0.0
     *
     * @see    sanitize_nested_data()
     */
    protected function sanitize_datasets( $datasets ) {
        if ( ! is_array( $datasets ) ) {
            return array();
        }

        $sanitized = array();
        foreach ( $datasets as $dataset ) {
            if ( ! is_array( $dataset ) ) {
                continue;
            }

            $sanitized_dataset = array();
            foreach ( $dataset as $key => $value ) {
                if ( is_string( $value ) ) {
                    $sanitized_dataset[ $key ] = esc_html( $value );
                } elseif ( is_array( $value ) && 'data' === $key ) {
                    $sanitized_dataset[ $key ] = $this->sanitize_nested_data( $value );
                } elseif ( is_array( $value ) ) {
                    $sanitized_dataset[ $key ] = $this->sanitize_array_values( $value );
                } else {
                    $sanitized_dataset[ $key ] = $value;
                }
            }
            $sanitized[] = $sanitized_dataset;
        }

        return $sanitized;
    }

    /**
     * Sanitize nested chart data
     *
     * Sanitizes nested data structures within chart datasets, handling
     * both simple arrays and multi-dimensional data structures.
     *
     * @param  array  $data  Nested data array
     * @return array         Sanitized nested data
     * @access protected
     * @since  1.0.0
     */
    protected function sanitize_nested_data( $data ) {
        $sanitized = array();
        foreach ( $data as $item ) {
            if ( is_array( $item ) ) {
                $sanitized[] = array_map( function( $v ) {
                    return is_string( $v ) ? esc_html( $v ) : $v;
                }, $item );
            } else {
                $sanitized[] = is_string( $item ) ? esc_html( $item ) : $item;
            }
        }
        return $sanitized;
    }

    /**
     * Sanitize chart data (alias)
     *
     * Alternative method for sanitizing chart data structures.
     * Maintains backward compatibility with existing code.
     *
     * @param  array  $data  Chart data to sanitize
     * @return array         Sanitized chart data
     * @access protected
     * @since  1.0.0
     */
    protected function sanitize_chart_data( $data ) {
        if ( ! is_array( $data ) ) {
            return array();
        }

        $sanitized = array();
        if ( isset( $data['labels'] ) ) {
            $sanitized['labels'] = $this->sanitize_array_values( $data['labels'] );
        }
        if ( isset( $data['datasets'] ) ) {
            $sanitized['datasets'] = $this->sanitize_datasets( $data['datasets'] );
        }

        return $sanitized;
    }

    /**
     * Sanitize chart options
     *
     * Recursively sanitizes Chart.js options configuration array.
     * Ensures all string values are properly escaped.
     *
     * @param  array  $options  Chart options to sanitize
     * @return array            Sanitized options
     * @access protected
     * @since  1.0.0
     */
    protected function sanitize_chart_options( $options ) {
        if ( ! is_array( $options ) ) {
            return array();
        }

        $sanitized = array();
        foreach ( $options as $key => $value ) {
            if ( is_string( $value ) ) {
                $sanitized[ $key ] = esc_html( $value );
            } elseif ( is_array( $value ) ) {
                $sanitized[ $key ] = $this->sanitize_chart_options( $value );
            } else {
                $sanitized[ $key ] = $value;
            }
        }

        return $sanitized;
    }

    /**
     * Render chart with error handling
     *
     * Safe wrapper for chart rendering that catches exceptions and
     * displays user-friendly error messages. Includes error logging
     * in debug mode.
     *
     * @param  array  $scheme   Chart scheme data
     * @param  array  $context  Rendering context data
     * @return string           Rendered HTML or error message
     * @access public
     * @static
     * @since  1.0.0
     *
     * @throws \Exception Propagated from render_chart() if not caught
     *
     * @example
     * // Safe chart rendering with error handling
     * echo ChartRenderer::render_chart_safe($scheme, $context);
     */
    public static function render_chart_safe( $scheme, $context ) {
        try {
            return self::render_chart( $scheme, $context );
        } catch ( \Exception $e ) {
            // Log error in debug mode
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                \UltraguardPro\core\Logger::error( 'Ultraguard Chart Render Error: ' . $e->getMessage() );
            }

            // Defer error action
            if ( has_action( 'ultraguard_chart_render_error' ) ) {
                add_action( 'shutdown', function() use ( $e, $scheme ) {
                    do_action( 'ultraguard_chart_render_error', $e->getMessage(), $scheme );
                }, 20 );
            }

            return '<div class="ultraguard-chart-error"><p>' .
                   esc_html__( 'Chart could not be loaded. Please try again later.', 'ultraguard-security-suite' ) .
                   '</p></div>';
        }
    }

    /**
     * Efficient array merging that handles numeric keys correctly
     *
     * Custom array merge implementation that properly handles numeric
     * keys and prevents duplicate values in merged arrays.
     *
     * @param  array  $array1  Base array
     * @param  array  $array2  Array to merge
     * @return array           Merged array
     * @access protected
     * @since  1.0.0
     */
    protected function array_merge_recursive_distinct( $array1, $array2 ) {
        $merged = $array1;

        foreach ( $array2 as $key => $value ) {
            if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
                $merged[ $key ] = $this->array_merge_recursive_distinct( $merged[ $key ], $value );
            } elseif ( is_numeric( $key ) ) {
                if ( ! in_array( $value, $merged, true ) ) {
                    $merged[] = $value;
                }
            } else {
                $merged[ $key ] = $value;
            }
        }

        return $merged;
    }

    /**
     * Alias for backward compatibility
     *
     * Maintains backward compatibility with existing code that uses
     * the older method name for chart options merging.
     *
     * @param  array  $array1  Base array
     * @param  array  $array2  Array to merge
     * @return array           Merged array
     * @access protected
     * @since  1.0.0
     * @deprecated Use array_merge_recursive_distinct() instead
     */
    protected function deep_merge_chart_options( $array1, $array2 ) {
        return $this->array_merge_recursive_distinct( $array1, $array2 );
    }

    /**
     * Clear static caches (useful for testing or memory management)
     *
     * Clears all static caches used by the ChartRenderer class.
     * Useful for unit testing or when memory management is critical.
     *
     * @return void
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * // Clear caches between test runs
     * ChartRenderer::clear_caches();
     */
    public static function clear_caches() {
        self::$config_cache = array();
        self::$security_color_schemes = array();
        self::$chart_assets_enqueued = false;
        self::$realtime_assets_enqueued = false;
    }

    /**
     * Render threat-heatmap component
     * Displays a calendar-style grid showing threat frequency by day.
     */
    public static function render_threat_heatmap( $scheme, $context ) {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        $scheme = wp_parse_args( $scheme, [
            'threat_data' => [],
            'period'      => '30days',
            'height'      => '150px',
            'title'       => '',
            'id'          => 'threat-heatmap-' . uniqid(),
            'class'       => '',
        ] );

        $threat_data = is_array( $scheme['threat_data'] ) ? $scheme['threat_data'] : [];
        $id_attr     = $scheme['id'] ? ' id="' . esc_attr( $scheme['id'] ) . '"' : '';
        $cls         = trim( 'ultraguard-threat-heatmap ' . esc_attr( $scheme['class'] ) );

        // Build a 30-day grid
        $days       = 30;
        $today      = strtotime( gmdate( 'Y-m-d' ) );
        $max_count  = max( 1, ! empty( $threat_data ) ? max( array_values( $threat_data ) ) : 1 );
        $cells      = '';

        for ( $i = $days - 1; $i >= 0; $i-- ) {
            $ts    = $today - ( $i * DAY_IN_SECONDS );
            $date  = gmdate( 'Y-m-d', $ts );
            $count = isset( $threat_data[ $date ] ) ? absint( $threat_data[ $date ] ) : 0;
            $ratio = $count / $max_count;

            // Intensity colour: light → dark red
            $alpha   = round( max( 0.08, min( 1.0, $ratio ) ), 2 );
            $bg      = $count > 0 ? "rgba(220,38,38,{$alpha})" : 'rgba(0,0,0,0.06)';
            $title   = esc_attr( $date . ': ' . $count . ' ' . ( $count === 1 ? 'threat' : 'threats' ) );

            $cells .= sprintf(
                '<div class="ultraguard-heatmap-cell" title="%s" style="background:%s;border-radius:2px;" data-date="%s" data-count="%d"></div>',
                $title, $bg, esc_attr( $date ), $count
            );
        }

        $title_html = ! empty( $scheme['title'] )
            ? '<div style="font-size:12px;font-weight:600;color:#64748b;margin-bottom:6px;">' . esc_html( $scheme['title'] ) . '</div>'
            : '';

        $output = sprintf(
            '<div%s class="%s">'
            . '%s'
            . '<div class="ultraguard-heatmap-grid" style="display:grid;grid-template-columns:repeat(%d,1fr);gap:3px;height:%s;">%s</div>'
            . '</div>',
            $id_attr, $cls,
            $title_html,
            $days,
            esc_attr( $scheme['height'] ),
            $cells
        );

        return $instance->after_render( $output, $scheme, $context );
    }

}
