<?php
/**
 * Grid System Renderer
 *
 * This file contains the GridRenderer class which provides a comprehensive
 * 12-column grid layout system with responsive design capabilities. It supports
 * multiple grid types including fixed columns, auto-fit, masonry, and grid areas.
 * The class includes caching mechanisms, input validation, and extensive
 * sanitization for secure output.
 *
 * @package    UltraguardPro\renderers
 * @author     Ultraguard Pro Team <developers@ultraguardpro.com>
 * @copyright  2024 Ultraguard Pro
 * @license    GPL-2.0-or-later
 * @version    1.0.0
 * @link       https://ultraguardpro.com/docs/grid-renderer
 */

namespace UltraguardPro\view;

defined( 'ABSPATH' ) || exit;

/**
 * Grid Renderer Class
 *
 * Handles the rendering of various grid layouts including 12-column responsive
 * grids, auto-fit grids, masonry layouts, and grid areas. The class implements
 * a singleton pattern with caching for performance optimization and includes
 * comprehensive input sanitization for security.
 *
 * This renderer supports multiple input formats for column spans (direct span,
 * fractions, "of" notation), responsive breakpoints, and allows for custom CSS
 * injection. It provides hooks for extensibility and is designed to work within
 * WordPress environments with proper escaping and validation.
 *
 * @package    UltraguardPro\view
 * @author     Ultraguard Pro Team
 * @version    1.0.0
 * @since      1.0.0
 * @see        BaseRenderer
 *
 * @example
 * // Basic 12-column grid
 * $grid = GridRenderer::render_grid([
 *     'columns' => 12,
 *     'gap' => '20px',
 *     'items' => [
 *         ['span' => 6, 'content' => 'Half width'],
 *         ['span' => 6, 'content' => 'Half width'],
 *     ],
 * ], $context);
 */
class GridRenderer extends BaseRenderer {

    /**
     * Singleton instance
     *
     * Stores the single instance of the GridRenderer class.
     * Ensures only one instance exists throughout the application.
     *
     * @var GridRenderer|null
     * @access private
     * @static
     */
    private static $instance = null;

    /**
     * CSS unit patterns cache
     *
     * Stores compiled regular expression patterns for validating
     * CSS units and grid template values. Prevents repeated
     * pattern compilation for performance.
     *
     * @var array<string, string|array>
     * @access private
     * @static
     */
    private static $css_patterns = array();

    /**
     * Grid calculation cache
     *
     * Caches rendered grid HTML and calculated spans to improve
     * performance for repeated calls with identical parameters.
     *
     * @var array<string, string>
     * @access private
     * @static
     */
    private static $grid_cache = array();

    /**
     * CSS sanitization cache
     *
     * Caches sanitized CSS values to avoid redundant processing
     * of identical CSS strings during rendering.
     *
     * @var array<string, string>
     * @access private
     * @static
     */
    private static $css_cache = array();

    /**
     * Default grid scheme
     *
     * Defines the default values for grid configuration when
     * parameters are not provided. Ensures consistent behavior
     * and prevents undefined index errors.
     *
     * @var array{
     *     columns: int,
     *     gap: string,
     *     responsive: bool,
     *     items: array,
     *     class: string,
     *     style: string,
     *     id: string,
     *     wrapper_class: string
     * }
     * @access private
     * @const
     */
    private const DEFAULT_GRID_SCHEME = array(
        'columns'        => 12,
        'gap'            => '20px',
        'responsive'     => true,
        'items'          => array(),
        'class'          => '',
        'style'          => '',
        'id'             => '',
        'wrapper_class'  => '',
    );

    /**
     * Default grid item scheme
     *
     * Defines the default values for individual grid items when
     * parameters are not provided. Provides fallback values for
     * span calculations and content rendering.
     *
     * @var array{
     *     span: int|null,
     *     fraction: string,
     *     of: string,
     *     responsive: array,
     *     class: string,
     *     content: string,
     *     component: array,
     *     data: array
     * }
     * @access private
     * @const
     */
    private const DEFAULT_ITEM_SCHEME = array(
        'span'        => null,
        'fraction'    => '',
        'of'          => '',
        'responsive'  => array(),
        'class'       => '',
        'content'     => '',
        'component'   => array(),
        'data'        => array(),
    );

    /**
     * Get singleton instance
     *
     * Returns the single instance of the GridRenderer class.
     * If no instance exists, creates one and initializes the
     * CSS patterns cache. Implements the singleton pattern.
     *
     * @return GridRenderer The singleton instance
     * @access public
     * @static
     * @since 1.0.0
     */
    public static function get_instance(): self {
        if ( null === self::$instance ) {
            self::$instance = new self();
            self::init_patterns();
        }
        return self::$instance;
    }

    /**
     * Initialize regex patterns
     *
     * Compiles and caches regular expression patterns used for
     * validating CSS units, fractions, and grid template values.
     * Called automatically when the singleton instance is created.
     *
     * @return void
     * @access private
     * @static
     * @since 1.0.0
     */
    private static function init_patterns(): void {
        if ( empty( self::$css_patterns ) ) {
            self::$css_patterns = array(
                'unit'         => '/^(\d*\.?\d+)(px|em|rem|%|vh|vw|vmin|vmax|fr|auto)$/',
                'fraction'     => '/^(\d+)\/(\d+)$/',
                'of_format'    => '/(\d+)\s*of\s*(\d+)/',
                'calc'         => '/calc\([^)]+\)/',
                'grid_template' => array(
                    '/^\d*\.?\d+fr$/',
                    '/^\d*\.?\d+(px|em|rem|%|vh|vw)$/',
                    '/^auto$/',
                    '/^minmax\([^)]+\)$/',
                    '/^repeat\(\d+,[^)]+\)$/',
                ),
            );
        }
    }

    /**
     * Render grid container
     *
     * Creates a responsive grid container with specified columns and gap.
     * Supports custom classes, styles, and IDs. Includes caching for
     * performance and extensive input validation for security.
     *
     * @param  array  $scheme   Grid configuration array
     * @param  array  $context  Context data for rendering
     * @return string           Rendered HTML grid container
     * @throws \Exception       If input validation fails
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * $html = GridRenderer::render_grid([
     *     'columns' => 12,
     *     'gap' => '20px',
     *     'items' => [
     *         ['span' => 4, 'content' => 'Column 1'],
     *         ['span' => 8, 'content' => 'Column 2'],
     *     ],
     * ], ['user_id' => 123]);
     */
    public static function render_grid( $scheme, $context ): string {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        // Secure with wp_parse_args
        $scheme = wp_parse_args( $scheme, self::DEFAULT_GRID_SCHEME );

        // Apply filters for extendability
        $scheme = apply_filters( 'ultraguard_grid_scheme', $scheme, $context );

        // Cache key for grid calculations
        $cache_key = 'grid_' . md5( wp_json_encode( array( $scheme, $context ) ) );

        if ( isset( self::$grid_cache[ $cache_key ] ) ) {
            return self::$grid_cache[ $cache_key ];
        }

        // Validate and sanitize inputs
        $columns    = max( 1, min( 24, absint( $scheme['columns'] ) ) );
        $gap        = $instance->sanitize_css_unit( $scheme['gap'] );
        $responsive = (bool) $scheme['responsive'];

        // Build CSS classes
        $css_classes = array( 'ultraguard-grid', 'ultraguard-grid-' . $columns );

        if ( $responsive ) {
            $css_classes[] = 'ultraguard-grid-responsive';
        }

        if ( ! empty( $scheme['class'] ) ) {
            $css_classes[] = sanitize_html_class( $scheme['class'] );
        }

        // Build grid styles
        $grid_style = sprintf(
            'display: grid; grid-template-columns: repeat(%d, minmax(0, 1fr)); gap: %s;',
            $columns,
            $gap
        );

        $full_style = $grid_style;
        if ( ! empty( $scheme['style'] ) ) {
            $full_style = $instance->sanitize_css( $scheme['style'] ) . '; ' . $grid_style;
        }

        // Render content
        $content = '';
        if ( ! empty( $scheme['items'] ) && is_array( $scheme['items'] ) ) {
            $content = $instance->render_grid_items( $scheme['items'], $context, $columns );
        }

        $output = $instance->render_wrapper( $content, array(
            'class' => implode( ' ', $css_classes ),
            'style' => $full_style,
            'id'    => ! empty( $scheme['id'] ) ? sanitize_html_class( $scheme['id'] ) : '',
        ), $context );

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

        self::$grid_cache[ $cache_key ] = $output;
        return $output;
    }

    /**
     * Render grid items
     *
     * Processes and renders individual grid items within a grid container.
     * Applies filters for extensibility and ensures proper item formatting
     * before passing to the item renderer.
     *
     * @param  array  $items          Array of grid item configurations
     * @param  array  $context        Context data for rendering
     * @param  int    $total_columns  Total number of grid columns (default: 12)
     * @return string                 Rendered HTML for all grid items
     * @access protected
     * @since  1.0.0
     *
     * @see render_grid_item()
     */
    protected function render_grid_items( $items, $context, int $total_columns = 12 ): string {
        if ( empty( $items ) || ! is_array( $items ) ) {
            return '';
        }

        // Allow filtering of grid items
        $items = apply_filters( 'ultraguard_grid_items', $items, $context, $total_columns );

        $output = array();
        foreach ( $items as $index => $item ) {
            if ( is_array( $item ) ) {
                $item   = apply_filters( 'ultraguard_grid_item', $item, $index, $context, $total_columns );
                $output[] = $this->render_single_grid_item( $item, $context, $total_columns );
            }
        }

        return implode( '', $output );
    }

    /**
     * Render single grid item
     *
     * Renders an individual grid item with calculated column span,
     * responsive classes, and proper data attributes. Includes caching
     * for identical item configurations.
     *
     * @param  array  $item           Grid item configuration array
     * @param  array  $context        Context data for rendering
     * @param  int    $total_columns  Total number of grid columns (default: 12)
     * @return string                 Rendered HTML for the grid item
     * @access protected
     * @since  1.0.0
     *
     * @see calculate_column_span()
     * @see get_item_content()
     * @see build_data_attributes()
     */
    protected function render_single_grid_item( $item, $context, int $total_columns = 12 ): string {
        // Secure item with defaults
        $item = wp_parse_args( $item, self::DEFAULT_ITEM_SCHEME );

        // Allow filtering of individual grid item
        $item = apply_filters( 'ultraguard_grid_item_scheme', $item, $context, $total_columns );

        // Cache key for item
        $cache_key = 'item_' . md5( wp_json_encode( array( $item, $context, $total_columns ) ) );

        if ( isset( self::$grid_cache[ $cache_key ] ) ) {
            return self::$grid_cache[ $cache_key ];
        }

        // Calculate column span
        $col_span = $this->calculate_column_span( $item, $total_columns );

        // Build item classes
        $item_classes = array( 'ultraguard-grid-item', 'ultraguard-col-' . $col_span );

        // Responsive classes
        if ( ! empty( $item['responsive'] ) && is_array( $item['responsive'] ) ) {
            foreach ( $item['responsive'] as $breakpoint => $span ) {
                if ( ! empty( $breakpoint ) && ! is_array( $span ) && ! is_object( $span ) ) {
                    $responsive_span = $this->calculate_responsive_span( $span, $total_columns );
                    $item_classes[] = 'ultraguard-col-' . sanitize_html_class( $breakpoint ) . '-' . $responsive_span;
                }
            }
        }

        // Add custom classes
        if ( ! empty( $item['class'] ) ) {
            $item_classes[] = sanitize_html_class( $item['class'] );
        }

        // Render item content
        $content = $this->get_item_content( $item, $context );

        // Allow content filtering
        $content = apply_filters( 'ultraguard_grid_item_content', $content, $item, $context );

        // Build item HTML
        $item_html = sprintf(
            '<div class="%s"%s>%s</div>',
            esc_attr( implode( ' ', $item_classes ) ),
            $this->build_data_attributes( $item['data'] ),
            $content
        );

        $item_html = apply_filters( 'ultraguard_grid_item_html', $item_html, $item, $context );

        self::$grid_cache[ $cache_key ] = $item_html;
        return $item_html;
    }

    /**
     * Get item content
     *
     * Retrieves and processes content for a grid item. Supports both
     * direct content strings and component-based rendering via the
     * template engine.
     *
     * @param  array  $item     Grid item configuration
     * @param  array  $context  Context data for rendering
     * @return string           Processed item content
     * @access private
     * @since  1.0.0
     *
     * @see BaseRenderer::escape()
     */
    private function get_item_content( array $item, array $context ): string {
        if ( ! empty( $item['content'] ) ) {
            if ( is_array( $item['content'] ) ) {
                return $this->engine->render( $item['content'], $context );
            }
            return $this->escape( $item['content'] );
        }

        if ( ! empty( $item['component'] ) && is_array( $item['component'] ) ) {
            return $this->engine->render( $item['component'], $context );
        }

        return '';
    }

    /**
     * Build data attributes
     *
     * Converts an array of data attributes into properly escaped
     * HTML data-* attributes. Ensures safe output and valid HTML.
     *
     * @param  array  $data  Associative array of data attributes
     * @return string        HTML string of data attributes
     * @access private
     * @since  1.0.0
     *
     * @example
     * $attrs = $this->build_data_attributes([
     *     'toggle' => 'modal',
     *     'target' => '#modal-1'
     * ]);
     * // Returns: data-toggle="modal" data-target="#modal-1"
     */
    private function build_data_attributes( array $data ): string {
        if ( empty( $data ) ) {
            return '';
        }

        $attributes = array();
        foreach ( $data as $key => $value ) {
            if ( ! is_array( $value ) && ! is_object( $value ) && ! empty( $key ) ) {
                $sanitized_key = sanitize_html_class( $key );
                $attributes[] = sprintf(
                    'data-%s="%s"',
                    esc_attr( $sanitized_key ),
                    esc_attr( $this->escape( $value, 'attribute' ) )
                );
            }
        }

        return $attributes ? ' ' . implode( ' ', $attributes ) : '';
    }

    /**
     * Calculate column span from various input formats
     *
     * Determines the column span for a grid item based on multiple
     * input formats: direct span numbers, fractions, or "of" notation.
     * Ensures the result is within valid bounds (1 to total_columns).
     *
     * @param  array  $item           Grid item configuration
     * @param  int    $total_columns  Total number of grid columns
     * @return int                    Calculated column span
     * @access protected
     * @since  1.0.0
     *
     * @see parse_fraction()
     * @see parse_of_format()
     */
    protected function calculate_column_span( $item, int $total_columns ): int {
        // Direct span
        if ( isset( $item['span'] ) && is_numeric( $item['span'] ) ) {
            return min( max( 1, absint( $item['span'] ) ), $total_columns );
        }

        // Fraction format
        if ( ! empty( $item['fraction'] ) && is_string( $item['fraction'] ) ) {
            $span = $this->parse_fraction( $item['fraction'], $total_columns );
            return min( max( 1, $span ), $total_columns );
        }

        // Of format
        if ( ! empty( $item['of'] ) && is_string( $item['of'] ) ) {
            $span = $this->parse_of_format( $item['of'], $total_columns );
            return min( max( 1, $span ), $total_columns );
        }

        // Default to full width
        return $total_columns;
    }

    /**
     * Parse fraction format
     *
     * Converts fraction strings (e.g., "1/2", "3/4") into column spans
     * based on the total number of columns. Validates the fraction
     * and ensures proper mathematical conversion.
     *
     * @param  string  $fraction      Fraction string (e.g., "1/2")
     * @param  int     $total_columns Total number of grid columns
     * @return int                    Calculated column span
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $span = $this->parse_fraction('1/2', 12); // Returns: 6
     */
    protected function parse_fraction( string $fraction, int $total_columns ): int {
        if ( empty( $fraction ) ) {
            return $total_columns;
        }

        if ( preg_match( self::$css_patterns['fraction'], $fraction, $matches ) ) {
            $numerator   = absint( $matches[1] );
            $denominator = absint( $matches[2] );

            if ( $numerator > 0 && $denominator > 0 && $numerator <= $denominator ) {
                $calculated = round( ( $numerator / $denominator ) * $total_columns );
                return max( 1, min( $calculated, $total_columns ) );
            }
        }

        return $total_columns;
    }

    /**
     * Parse "of" format
     *
     * Converts "of" notation strings (e.g., "3 of 4", "2 of 12") into
     * column spans based on the total number of columns.
     *
     * @param  string  $of_format     "of" notation string
     * @param  int     $total_columns Total number of grid columns
     * @return int                    Calculated column span
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $span = $this->parse_of_format('3 of 4', 12); // Returns: 9
     */
    protected function parse_of_format( string $of_format, int $total_columns ): int {
        if ( empty( $of_format ) ) {
            return $total_columns;
        }

        if ( preg_match( self::$css_patterns['of_format'], $of_format, $matches ) ) {
            $span      = absint( $matches[1] );
            $of_total  = absint( $matches[2] );

            if ( $span > 0 && $of_total > 0 ) {
                $calculated = round( ( $span / $of_total ) * $total_columns );
                return max( 1, min( $calculated, $total_columns ) );
            }
        }

        return $total_columns;
    }

    /**
     * Calculate responsive span
     *
     * Calculates column span for responsive breakpoints. Supports
     * numeric values, fraction strings, and "of" notation for
     * flexible responsive design.
     *
     * @param  mixed  $span           Span configuration (int, string)
     * @param  int    $total_columns  Total number of grid columns
     * @return int                    Calculated responsive span
     * @access protected
     * @since  1.0.0
     *
     * @see parse_fraction()
     * @see parse_of_format()
     */
    protected function calculate_responsive_span( $span, int $total_columns ): int {
        if ( is_numeric( $span ) ) {
            return min( max( 1, absint( $span ) ), $total_columns );
        }

        if ( is_string( $span ) ) {
            if ( strpos( $span, '/' ) !== false ) {
                return $this->parse_fraction( $span, $total_columns );
            }

            if ( strpos( $span, 'of' ) !== false ) {
                return $this->parse_of_format( $span, $total_columns );
            }
        }

        return $total_columns;
    }

    /**
     * Render auto grid
     *
     * Creates a grid with auto-fit columns that adjust based on
     * minimum width. Useful for responsive card layouts where
     * items wrap to fit available space.
     *
     * @param  array  $scheme   Auto grid configuration
     * @param  array  $context  Context data for rendering
     * @return string           Rendered HTML auto grid
     * @throws \Exception       If input validation fails
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * $html = GridRenderer::render_auto_grid([
     *     'min_width' => '250px',
     *     'gap' => '20px',
     *     'items' => [
     *         ['content' => 'Card 1'],
     *         ['content' => 'Card 2'],
     *     ],
     * ], $context);
     */
    public static function render_auto_grid( $scheme, $context ): string {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        // Secure with wp_parse_args
        $scheme = wp_parse_args( $scheme, array(
            'min_width'      => '250px',
            'gap'            => '20px',
            'items'          => array(),
            'class'          => '',
            'style'          => '',
            'id'             => '',
            'wrapper_class'  => '',
        ) );

        // Apply filters
        $scheme = apply_filters( 'ultraguard_auto_grid_scheme', $scheme, $context );

        // Cache key
        $cache_key = 'auto_' . md5( wp_json_encode( array( $scheme, $context ) ) );

        if ( isset( self::$grid_cache[ $cache_key ] ) ) {
            return self::$grid_cache[ $cache_key ];
        }

        $min_width = $instance->sanitize_css_unit( $scheme['min_width'] );
        $gap       = $instance->sanitize_css_unit( $scheme['gap'] );

        $css_classes = array( 'ultraguard-auto-grid' );
        if ( ! empty( $scheme['class'] ) ) {
            $css_classes[] = sanitize_html_class( $scheme['class'] );
        }

        $grid_style = sprintf(
            'display: grid; grid-template-columns: repeat(auto-fit, minmax(%s, 1fr)); gap: %s;',
            $min_width,
            $gap
        );

        $full_style = $grid_style;
        if ( ! empty( $scheme['style'] ) ) {
            $full_style = $instance->sanitize_css( $scheme['style'] ) . '; ' . $grid_style;
        }

        $content = '';
        if ( ! empty( $scheme['items'] ) && is_array( $scheme['items'] ) ) {
            $items = array();
            foreach ( $scheme['items'] as $index => $item ) {
                if ( is_array( $item ) ) {
                    $item = apply_filters( 'ultraguard_auto_grid_item', $item, $index, $context );
                    $items[] = $instance->render_grid_item( $item, $context, 12 );
                }
            }
            $content = implode( '', $items );
        }

        $output = $instance->render_wrapper( $content, array(
            'class' => implode( ' ', $css_classes ),
            'style' => $full_style,
            'id'    => ! empty( $scheme['id'] ) ? sanitize_html_class( $scheme['id'] ) : '',
        ), $context );

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

        self::$grid_cache[ $cache_key ] = $output;
        return $output;
    }

    /**
     * Render masonry grid
     *
     * Creates a masonry-style grid layout with fixed columns and
     * variable row spans. Useful for Pinterest-style layouts where
     * items have different heights.
     *
     * @param  array  $scheme   Masonry grid configuration
     * @param  array  $context  Context data for rendering
     * @return string           Rendered HTML masonry grid
     * @throws \Exception       If input validation fails
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * $html = GridRenderer::render_masonry_grid([
     *     'columns' => 3,
     *     'gap' => '20px',
     *     'items' => [
     *         ['span_rows' => 2, 'content' => 'Tall item'],
     *         ['span_rows' => 1, 'content' => 'Short item'],
     *     ],
     * ], $context);
     */
    public static function render_masonry_grid( $scheme, $context ): string {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        // Secure with wp_parse_args
        $scheme = wp_parse_args( $scheme, array(
            'columns'        => 3,
            'gap'            => '20px',
            'items'          => array(),
            'class'          => '',
            'style'          => '',
            'id'             => '',
            'wrapper_class'  => '',
        ) );

        // Apply filters
        $scheme = apply_filters( 'ultraguard_masonry_grid_scheme', $scheme, $context );

        // Cache key
        $cache_key = 'masonry_' . md5( wp_json_encode( array( $scheme, $context ) ) );

        if ( isset( self::$grid_cache[ $cache_key ] ) ) {
            return self::$grid_cache[ $cache_key ];
        }

        $columns = max( 1, min( 12, absint( $scheme['columns'] ) ) );
        $gap     = $instance->sanitize_css_unit( $scheme['gap'] );

        $css_classes = array( 'ultraguard-masonry-grid' );
        if ( ! empty( $scheme['class'] ) ) {
            $css_classes[] = sanitize_html_class( $scheme['class'] );
        }

        $grid_style = sprintf(
            'display: grid; grid-template-columns: repeat(%d, minmax(0, 1fr)); gap: %s; grid-auto-rows: 0;',
            $columns,
            $gap
        );

        $full_style = $grid_style;
        if ( ! empty( $scheme['style'] ) ) {
            $full_style = $instance->sanitize_css( $scheme['style'] ) . '; ' . $grid_style;
        }

        $content = '';
        if ( ! empty( $scheme['items'] ) && is_array( $scheme['items'] ) ) {
            $items = array();
            foreach ( $scheme['items'] as $index => $item ) {
                if ( is_array( $item ) ) {
                    $item = wp_parse_args( $item, array(
                        'span_rows' => 1,
                        'class'     => '',
                    ) );

                    $item_classes = array( 'ultraguard-masonry-item' );
                    $item_classes[] = 'ultraguard-masonry-span-' . absint( $item['span_rows'] );

                    if ( ! empty( $item['class'] ) ) {
                        $item_classes[] = sanitize_html_class( $item['class'] );
                    }

                    $item_content = $instance->get_item_content( $item, $context );
                    $item_content = apply_filters( 'ultraguard_masonry_item_content', $item_content, $item, $context );

                    $items[] = sprintf(
                        '<div class="%s">%s</div>',
                        esc_attr( implode( ' ', $item_classes ) ),
                        $item_content
                    );
                }
            }
            $content = implode( '', $items );
        }

        $output = $instance->render_wrapper( $content, array(
            'class' => implode( ' ', $css_classes ),
            'style' => $full_style,
            'id'    => ! empty( $scheme['id'] ) ? sanitize_html_class( $scheme['id'] ) : '',
        ), $context );

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

        self::$grid_cache[ $cache_key ] = $output;
        return $output;
    }

    /**
     * Render grid with custom areas
     *
     * Creates a CSS Grid layout with named template areas for complex
     * page layouts. Allows precise placement of content in defined
     * grid areas.
     *
     * @param  array  $scheme   Grid areas configuration
     * @param  array  $context  Context data for rendering
     * @return string           Rendered HTML grid with areas
     * @throws \Exception       If input validation fails
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * $html = GridRenderer::render_grid_areas([
     *     'template_areas' => [
     *         ['header', 'header', 'header'],
     *         ['sidebar', 'content', 'content'],
     *     ],
     *     'areas' => [
     *         ['name' => 'header', 'content' => 'Header content'],
     *         ['name' => 'sidebar', 'content' => 'Sidebar content'],
     *     ],
     * ], $context);
     */
    public static function render_grid_areas( $scheme, $context ): string {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        // Secure with wp_parse_args
        $scheme = wp_parse_args( $scheme, array(
            'template_areas'   => array(),
            'template_columns' => '1fr',
            'template_rows'    => 'auto',
            'gap'              => '20px',
            'areas'            => array(),
            'class'            => '',
            'style'            => '',
            'id'               => '',
            'wrapper_class'    => '',
        ) );

        // Apply filters
        $scheme = apply_filters( 'ultraguard_grid_areas_scheme', $scheme, $context );

        // Cache key
        $cache_key = 'areas_' . md5( wp_json_encode( array( $scheme, $context ) ) );

        if ( isset( self::$grid_cache[ $cache_key ] ) ) {
            return self::$grid_cache[ $cache_key ];
        }

        $template_columns = $instance->sanitize_css_grid_template( $scheme['template_columns'] );
        $template_rows    = $instance->sanitize_css_grid_template( $scheme['template_rows'] );
        $gap              = $instance->sanitize_css_unit( $scheme['gap'] );

        $css_classes = array( 'ultraguard-grid-areas' );
        if ( ! empty( $scheme['class'] ) ) {
            $css_classes[] = sanitize_html_class( $scheme['class'] );
        }

        // Build grid template
        $grid_style = sprintf(
            'display: grid; grid-template-columns: %s; grid-template-rows: %s; gap: %s;',
            $template_columns,
            $template_rows,
            $gap
        );

        // Add template areas if provided
        if ( ! empty( $scheme['template_areas'] ) && is_array( $scheme['template_areas'] ) ) {
            $area_strings = array();
            foreach ( $scheme['template_areas'] as $area ) {
                if ( is_array( $area ) ) {
                    $area_strings[] = '"' . implode( ' ', array_map( 'sanitize_html_class', $area ) ) . '"';
                }
            }

            if ( ! empty( $area_strings ) ) {
                $grid_style .= ' grid-template-areas: ' . implode( ' ', $area_strings ) . ';';
            }
        }

        $full_style = $grid_style;
        if ( ! empty( $scheme['style'] ) ) {
            $full_style = $instance->sanitize_css( $scheme['style'] ) . '; ' . $grid_style;
        }

        $content = '';
        if ( ! empty( $scheme['areas'] ) && is_array( $scheme['areas'] ) ) {
            $areas = array();
            foreach ( $scheme['areas'] as $index => $area ) {
                $area = wp_parse_args( $area, array(
                    'name'      => '',
                    'class'     => '',
                    'content'   => '',
                    'component' => array(),
                ) );

                if ( ! empty( $area['name'] ) ) {
                    $area_classes = array( 'ultraguard-grid-area' );
                    $area_classes[] = 'ultraguard-area-' . sanitize_html_class( $area['name'] );

                    if ( ! empty( $area['class'] ) ) {
                        $area_classes[] = sanitize_html_class( $area['class'] );
                    }

                    $area_content = $instance->get_item_content( $area, $context );
                    $area_content = apply_filters( 'ultraguard_grid_area_content', $area_content, $area, $context );

                    $areas[] = sprintf(
                        '<div class="%s" style="grid-area: %s;">%s</div>',
                        esc_attr( implode( ' ', $area_classes ) ),
                        esc_attr( sanitize_html_class( $area['name'] ) ),
                        $area_content
                    );
                }
            }
            $content = implode( '', $areas );
        }

        $output = $instance->render_wrapper( $content, array(
            'class' => implode( ' ', $css_classes ),
            'style' => $full_style,
            'id'    => ! empty( $scheme['id'] ) ? sanitize_html_class( $scheme['id'] ) : '',
        ), $context );

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

        self::$grid_cache[ $cache_key ] = $output;
        return $output;
    }

    /**
     * Sanitize CSS unit values
     *
     * Validates and sanitizes CSS unit values (px, em, rem, %, vh, vw, etc.)
     * and calc() functions. Returns a safe CSS value or default fallback.
     *
     * @param  string  $value  CSS value to sanitize
     * @return string          Sanitized CSS value
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $safe = $this->sanitize_css_unit('20px'); // Returns: '20px'
     * $safe = $this->sanitize_css_unit('calc(50% - 10px)'); // Returns sanitized calc
     */
    protected function sanitize_css_unit( string $value ): string {
        if ( empty( $value ) ) {
            return '0';
        }

        // Check cache
        $cache_key = 'unit_' . $value;
        if ( isset( self::$css_cache[ $cache_key ] ) ) {
            return self::$css_cache[ $cache_key ];
        }

        // Allow common CSS units
        if ( preg_match( self::$css_patterns['unit'], $value, $matches ) ) {
            self::$css_cache[ $cache_key ] = $matches[1] . $matches[2];
            return self::$css_cache[ $cache_key ];
        }

        // Allow calc() functions
        if ( strpos( $value, 'calc(' ) === 0 && substr( $value, -1 ) === ')' ) {
            $clean_value = preg_replace( '/[^0-9+\-*/().% pxemremvhvwvminvmax]/', '', $value );
            self::$css_cache[ $cache_key ] = $clean_value ?: '20px';
            return self::$css_cache[ $cache_key ];
        }

        // Default fallback
        self::$css_cache[ $cache_key ] = '20px';
        return self::$css_cache[ $cache_key ];
    }

    /**
     * Sanitize CSS grid template values
     *
     * Validates and sanitizes CSS grid template values including
     * fr units, pixel values, auto, minmax(), and repeat() functions.
     *
     * @param  string  $value  Grid template value to sanitize
     * @return string          Sanitized grid template value
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $safe = $this->sanitize_css_grid_template('1fr 2fr auto');
     * $safe = $this->sanitize_css_grid_template('repeat(3, minmax(100px, 1fr))');
     */
    protected function sanitize_css_grid_template( string $value ): string {
        if ( empty( $value ) ) {
            return '1fr';
        }

        $cache_key = 'template_' . $value;
        if ( isset( self::$css_cache[ $cache_key ] ) ) {
            return self::$css_cache[ $cache_key ];
        }

        // Check allowed patterns
        foreach ( self::$css_patterns['grid_template'] as $pattern ) {
            if ( preg_match( $pattern, $value ) ) {
                self::$css_cache[ $cache_key ] = $value;
                return $value;
            }
        }

        // For complex templates
        $values = explode( ' ', $value );
        $sanitized_values = array();

        foreach ( $values as $val ) {
            foreach ( self::$css_patterns['grid_template'] as $pattern ) {
                if ( preg_match( $pattern, $val ) ) {
                    $sanitized_values[] = $val;
                    break;
                }
            }
        }

        $result = ! empty( $sanitized_values ) ? implode( ' ', $sanitized_values ) : '1fr';
        self::$css_cache[ $cache_key ] = $result;

        return $result;
    }

    /**
     * Sanitize CSS style string
     *
     * Removes dangerous patterns from CSS strings including
     * expression(), javascript:, vbscript:, @import, and url().
     * Ensures safe CSS injection.
     *
     * @param  string  $css  CSS style string to sanitize
     * @return string        Sanitized CSS string
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $safe = $this->sanitize_css('color: red; expression(alert(1));');
     * // Returns: 'color: red;'
     */
    protected function sanitize_css( string $css ): string {
        if ( empty( $css ) ) {
            return '';
        }

        $cache_key = 'css_' . md5( $css );
        if ( isset( self::$css_cache[ $cache_key ] ) ) {
            return self::$css_cache[ $cache_key ];
        }

        // Remove dangerous patterns
        $dangerous_patterns = array(
            '/expression\s*\(/i',
            '/javascript\s*:/i',
            '/vbscript\s*:/i',
            '/@import/i',
            '/url\s*\([^)]*\)/i',
        );

        $safe_css = $css;
        foreach ( $dangerous_patterns as $pattern ) {
            $safe_css = preg_replace( $pattern, '', $safe_css );
        }

        $result = wp_strip_all_tags( $safe_css );
        self::$css_cache[ $cache_key ] = $result;

        return $result;
    }

    /**
     * Escape output based on context
     *
     * Applies appropriate WordPress escaping functions based on
     * the output context (HTML, attribute, URL, JavaScript).
     * Ensures safe output for different contexts.
     *
     * @param  mixed   $value    Value to escape
     * @param  string  $context  Escape context ('html', 'attribute', 'url', 'js')
     * @return string            Escaped value
     * @access protected
     * @since  1.0.0
     *
     * @see esc_html()
     * @see esc_attr()
     * @see esc_url()
     * @see esc_js()
     */
    protected function escape( $value, $context = 'html' ) {
        switch ( $context ) {
            case 'attribute':
                return esc_attr( $value );
            case 'url':
                return esc_url( $value );
            case 'js':
                return esc_js( $value );
            case 'html':
            default:
                return esc_html( $value );
        }
    }

    /**
     * Clear cache (for testing/debugging)
     *
     * Clears all internal caches for testing purposes or when
     * debugging cache-related issues. Not typically needed in
     * production environments.
     *
     * @return void
     * @access public
     * @static
     * @since  1.0.0
     */
    public static function clear_cache(): void {
        self::$grid_cache = array();
        self::$css_cache = array();
    }

    /**
     * Render grid-container
     * Semantic wrapper that applies display:grid with configurable column template.
     */
    public static function render_grid_container( $scheme, $context ): string {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        $scheme = wp_parse_args( $scheme, [
            'columns'  => 12,
            'gap'      => 'var(--ug-spacing-lg,24px)',
            'template' => '',
            'items'    => [],
            'id'       => '',
            'class'    => '',
        ] );

        // Delegate to render_grid — identical semantics
        return self::render_grid( $scheme, $context );
    }

    /**
     * Render grid-item
     * Individual grid item with configurable column span.
     */
    public static function render_grid_item( $scheme, $context ): string {
        $instance = self::get_instance();
        $scheme   = $instance->before_render( $scheme, $context );

        $scheme = wp_parse_args( $scheme, [
            'span'      => 12,
            'content'   => '',
            'component' => [],
            'id'        => '',
            'class'     => '',
        ] );

        $engine  = \UltraguardPro\view\RenderEngine::get_instance();
        $content = '';

        if ( ! empty( $scheme['component'] ) && is_array( $scheme['component'] ) ) {
            $content = $engine->render( $scheme['component'], $context );
        } elseif ( ! empty( $scheme['content'] ) ) {
            $content = wp_kses_post( $scheme['content'] );
        }

        $span    = max( 1, min( 12, (int) $scheme['span'] ) );
        $id_attr = $scheme['id'] ? ' id="' . esc_attr( $scheme['id'] ) . '"' : '';
        $classes = trim( 'ultraguard-grid-item ultraguard-col-' . $span . ' ' . esc_attr( $scheme['class'] ) );

        return sprintf(
            '<div%s class="%s" style="grid-column:span %d;min-width:0;">%s</div>',
            $id_attr, $classes, $span, $content
        );
    }

}
