<?php
/**
 * Base Renderer - Performance Optimized
 *
 * This file contains the BaseRenderer class which provides common rendering
 * functionality for all renderers in the UltraguardPro system. It includes
 * methods for sanitization, error handling, wrapper rendering, and child
 * component rendering with performance optimizations.
 *
 * The class implements caching mechanisms to avoid repeated processing and
 * provides hooks for extensibility through WordPress filters. It serves as
 * the foundation for all specific renderer implementations in the plugin.
 *
 * @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/renderers
 */

namespace UltraguardPro\view;

defined( 'ABSPATH' ) || exit;

/**
 * Base Renderer - Performance Optimized
 *
 * Provides common functionality for all renderers including sanitization,
 * wrapper rendering, child component rendering, and error handling. Designed
 * with performance optimizations through static caching and efficient
 * processing patterns.
 *
 * This class implements the Template Method pattern, allowing derived classes
 * to override specific rendering behaviors while maintaining consistent
 * security and error handling. It integrates with WordPress's sanitization
 * and escaping functions to ensure secure output.
 *
 * Key features:
 * - Static caching for default schemes and sanitization results
 * - Comprehensive HTML escaping based on context
 * - WordPress filter hooks for extensibility
 * - User capability checking for conditional rendering
 * - Error handling with debug information
 * - Wrapper element generation with configurable attributes
 *
 * @package    UltraguardPro\view
 * @author     Ultraguard Team
 * @version    1.0.0
 * @since      1.0.0
 * @see        \UltraguardPro\view\RenderEngine
 *
 * @example
 * // In a derived renderer class:
 * class MyRenderer extends BaseRenderer {
 *     public function render($scheme, $context) {
 *         $scheme = $this->before_render($scheme, $context);
 *         if (!$this->user_can_render($scheme, $context)) {
 *             return '';
 *         }
 *         $content = $this->render_content($scheme, $context);
 *         $output = $this->render_wrapper($content, $scheme, $context);
 *         return $this->after_render($output, $scheme, $context);
 *     }
 * }
 */
class BaseRenderer {

    /**
     * Render engine instance
     *
     * Handles the actual rendering of components. Provides access to
     * registered renderers and manages the rendering pipeline.
     *
     * @var \UltraguardPro\view\RenderEngine
     * @access protected
     */
    protected $engine;

    /**
     * Cached default scheme array
     *
     * Default values for component schemes. Used to ensure all required
     * keys exist and to provide fallback values for missing data.
     *
     * @var array
     * @access private
     * @static
     */
    private static $default_scheme = [
        'type' => 'unknown',
        'id' => '',
        'class' => '',
        'style' => '',
        'data' => []
    ];

    /**
     * Cached default context array
     *
     * Default values for rendering context. Provides fallback values
     * for render mode and user capability settings.
     *
     * @var array
     * @access private
     * @static
     */
    private static $default_context = [
        'render_mode' => 'normal',
        'user_capability' => 'read'
    ];

    /**
     * Cached default wrapper scheme array
     *
     * Default values for wrapper element configuration. Defines the
     * structure and allowed attributes for wrapper elements.
     *
     * @var array
     * @access private
     * @static
     */
    private static $default_wrapper_scheme = [
        'id' => '',
        'class' => '',
        'style' => '',
        'data' => [],
        'wrapper_tag' => 'div',
        'attributes' => []
    ];

    /**
     * Cached default capability scheme array
     *
     * Default values for user capability checking. Defines the structure
     * for permission-based rendering restrictions.
     *
     * @var array
     * @access private
     * @static
     */
    private static $default_capability_scheme = [
        'capability' => 'read',
        'roles' => [],
        'users' => []
    ];

    /**
     * Cached default error scheme array
     *
     * Default values for error handling schemes. Provides fallback
     * values when rendering errors occur.
     *
     * @var array
     * @access private
     * @static
     */
    private static $default_error_scheme = [
        'type' => 'unknown',
        'id' => 'unknown'
    ];

    /**
     * Allowed wrapper HTML tags
     *
     * List of valid HTML tags that can be used as wrapper elements.
     * Prevents unsafe tags from being used in rendered output.
     *
     * @var array<string>
     * @access private
     * @static
     */
    private static $allowed_wrapper_tags = [
        'div', 'span', 'section', 'article', 'main', 'header', 'footer', 'aside'
    ];

    /**
     * Sanitization cache to avoid repeated processing
     *
     * Stores sanitized results keyed by input hash to prevent redundant
     * processing of identical data. Improves performance for repeated
     * renders of the same content.
     *
     * @var array
     * @access private
     * @static
     */
    private static $sanitization_cache = [];

    /**
     * Shared icon map for translating common Dashicons into more expressive
     * Font Awesome equivalents inside the UltraGuard UI.
     *
     * @var array<string, string>
     * @access private
     * @static
     */
    private static $icon_class_map = [
        'dashicons-admin-appearance' => 'fa-solid fa-palette',
        'dashicons-admin-comments'   => 'fa-solid fa-comments',
        'dashicons-admin-generic'    => 'fa-solid fa-gear',
        'dashicons-admin-home'       => 'fa-solid fa-house',
        'dashicons-admin-links'      => 'fa-solid fa-link',
        'dashicons-admin-media'      => 'fa-solid fa-photo-film',
        'dashicons-admin-network'    => 'fa-solid fa-network-wired',
        'dashicons-admin-page'       => 'fa-solid fa-file-lines',
        'dashicons-admin-plugins'    => 'fa-solid fa-plug',
        'dashicons-admin-post'       => 'fa-solid fa-newspaper',
        'dashicons-admin-settings'   => 'fa-solid fa-sliders',
        'dashicons-admin-site'       => 'fa-solid fa-globe',
        'dashicons-admin-tools'      => 'fa-solid fa-screwdriver-wrench',
        'dashicons-admin-users'      => 'fa-solid fa-users',
        'dashicons-arrow-down'       => 'fa-solid fa-arrow-down',
        'dashicons-arrow-up'         => 'fa-solid fa-arrow-up',
        'dashicons-backup'           => 'fa-solid fa-box-archive',
        'dashicons-chart-area'       => 'fa-solid fa-chart-area',
        'dashicons-chart-bar'        => 'fa-solid fa-chart-column',
        'dashicons-cloud'            => 'fa-solid fa-cloud',
        'dashicons-dashboard'        => 'fa-solid fa-gauge-high',
        'dashicons-database'         => 'fa-solid fa-database',
        'dashicons-dismiss'          => 'fa-solid fa-circle-xmark',
        'dashicons-download'         => 'fa-solid fa-download',
        'dashicons-edit'             => 'fa-solid fa-pen-to-square',
        'dashicons-editor-help'      => 'fa-solid fa-circle-question',
        'dashicons-flag'             => 'fa-solid fa-flag',
        'dashicons-grid-view'        => 'fa-solid fa-table-cells-large',
        'dashicons-info'             => 'fa-solid fa-circle-info',
        'dashicons-list-view'        => 'fa-solid fa-list',
        'dashicons-lock'             => 'fa-solid fa-lock',
        'dashicons-media-code'       => 'fa-solid fa-code',
        'dashicons-media-document'   => 'fa-solid fa-file-lines',
        'dashicons-minus'            => 'fa-solid fa-minus',
        'dashicons-no'               => 'fa-solid fa-circle-xmark',
        'dashicons-no-alt'           => 'fa-solid fa-circle-xmark',
        'dashicons-performance'      => 'fa-solid fa-gauge-simple-high',
        'dashicons-randomize'        => 'fa-solid fa-shuffle',
        'dashicons-rest-api'         => 'fa-solid fa-code-branch',
        'dashicons-search'           => 'fa-solid fa-magnifying-glass',
        'dashicons-shield'           => 'fa-solid fa-shield-halved',
        'dashicons-shield-alt'       => 'fa-solid fa-shield-halved',
        'dashicons-trash'            => 'fa-solid fa-trash',
        'dashicons-unlock'           => 'fa-solid fa-lock-open',
        'dashicons-update'           => 'fa-solid fa-rotate',
        'dashicons-upload'           => 'fa-solid fa-upload',
        'dashicons-visibility'       => 'fa-solid fa-eye',
        'dashicons-warning'          => 'fa-solid fa-triangle-exclamation',
        'dashicons-yes'              => 'fa-solid fa-circle-check',
        'dashicons-yes-alt'          => 'fa-solid fa-circle-check',
    ];

    /**
     * BaseRenderer constructor
     *
     * Initializes the renderer by obtaining an instance of the
     * RenderEngine. Sets up the rendering infrastructure for
     * processing component schemes.
     *
     * @return void
     * @access public
     */
    public function __construct() {
        $this->engine = RenderEngine::get_instance();
    }

    /**
     * Sanitize a CSS class list while preserving multiple classes.
     *
     * @param  string $class_list Raw class list
     * @return string
     * @access protected
     */
    protected function sanitize_class_list( $class_list ) {
        if ( ! is_string( $class_list ) || '' === trim( $class_list ) ) {
            return '';
        }

        $classes = preg_split( '/\s+/', trim( $class_list ) );
        $classes = array_filter( array_map( 'sanitize_html_class', $classes ) );

        return implode( ' ', $classes );
    }

    /**
     * Determine whether an icon string points to a Font Awesome icon.
     *
     * @param  string $icon Icon string
     * @return bool
     * @access protected
     */
    protected function is_fontawesome_icon( $icon ) {
        if ( ! is_string( $icon ) || '' === trim( $icon ) ) {
            return false;
        }

        return false !== strpos( $icon, 'fa-' ) || 1 === preg_match( '/(^|\s)(fa|fas|far|fab|fal|fad|fat)(\s|$)/', $icon );
    }

    /**
     * Normalize icon identifiers so legacy Dashicons can render through the
     * richer Font Awesome set where it improves clarity.
     *
     * @param  string $icon Raw icon identifier
     * @return string
     * @access protected
     */
    protected function normalize_icon_identifier( $icon ) {
        $icon = is_string( $icon ) ? trim( $icon ) : '';

        if ( '' === $icon ) {
            return '';
        }

        if ( isset( self::$icon_class_map[ $icon ] ) ) {
            return self::$icon_class_map[ $icon ];
        }

        return preg_replace( '/\s+/', ' ', $icon );
    }

    /**
     * Build an HTML-safe attribute string.
     *
     * @param  array<string, mixed> $attributes Attributes
     * @return string
     * @access protected
     */
    protected function build_attribute_string( $attributes ) {
        if ( empty( $attributes ) || ! is_array( $attributes ) ) {
            return '';
        }

        $parts = [];

        foreach ( $attributes as $key => $value ) {
            if ( ! is_string( $key ) || '' === $key || null === $value || '' === $value ) {
                continue;
            }

            if ( is_bool( $value ) ) {
                if ( $value ) {
                    $parts[] = esc_attr( $key );
                }
                continue;
            }

            $parts[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( (string) $value ) );
        }

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

    /**
     * Render a UI icon with shared Dashicons / Font Awesome handling.
     *
     * Existing Dashicon-based component CSS remains compatible because Font
     * Awesome icons are wrapped in the same span contract.
     *
     * @param  string               $icon          Icon identifier
     * @param  string               $extra_classes Additional wrapper classes
     * @param  array<string, mixed> $attributes    Optional HTML attributes
     * @return string
     * @access protected
     */
    protected function render_ui_icon( $icon, $extra_classes = '', $attributes = [] ) {
        $icon = is_string( $icon ) ? trim( $icon ) : '';

        if ( '' === $icon ) {
            return '';
        }

        $normalized_icon = $this->normalize_icon_identifier( $icon );
        $extra_classes   = $this->sanitize_class_list( $extra_classes );
        $attributes_html = $this->build_attribute_string( $attributes );

        if ( $this->is_fontawesome_icon( $normalized_icon ) ) {
            $wrapper_classes = trim( 'dashicons ultraguard-fa-icon ' . $extra_classes );

            return sprintf(
                '<span class="%s"%s><i class="%s" aria-hidden="true"></i></span>',
                esc_attr( $wrapper_classes ),
                $attributes_html,
                esc_attr( $this->sanitize_class_list( $normalized_icon ) )
            );
        }

        $wrapper_classes = trim( 'dashicons ' . sanitize_html_class( $normalized_icon ) . ' ' . $extra_classes );

        return sprintf(
            '<span class="%s"%s></span>',
            esc_attr( $wrapper_classes ),
            $attributes_html
        );
    }

    /**
     * Before render hook
     *
     * Processes scheme and context data before rendering. Applies
     * default values, sanitizes input, and executes WordPress filters
     * for extensibility. This method should be called at the beginning
     * of any render method.
     *
     * @param  array  $scheme   Component scheme data
     * @param  array  $context  Rendering context data
     * @return array            Processed scheme data
     * @access protected
     * @since  1.0.0
     *
     * @see    after_render()
     *
     * @example
     * $processed_scheme = $this->before_render(
     *     ['type' => 'button', 'id' => 'submit-btn'],
     *     ['render_mode' => 'preview']
     * );
     */
    protected function before_render($scheme, $context): array {
        if (!is_array($scheme) || !is_array($context)) {
            return self::$default_scheme;
        }

        $scheme = wp_parse_args($scheme, self::$default_scheme);
        $context = wp_parse_args($context, self::$default_context);

        $scheme = $this->sanitize_scheme($scheme);
        $context = $this->sanitize_context($context);

        // Check if filters are hooked before calling them
        if (has_filter('ultraguard_before_render')) {
            $scheme = apply_filters('ultraguard_before_render', $scheme, $context);
        }

        $scheme_type = $scheme['type'] ?? '';
        if ($scheme_type && $scheme_type !== 'unknown' && has_filter("ultraguard_before_render_{$scheme_type}")) {
            $scheme = apply_filters("ultraguard_before_render_{$scheme_type}", $scheme, $context);
        }

        $scheme_id = $scheme['id'] ?? '';
        if ($scheme_id && has_filter("ultraguard_before_render_{$scheme_id}")) {
            $scheme = apply_filters("ultraguard_before_render_{$scheme_id}", $scheme, $context);
        }

        return $scheme;
    }

    /**
     * After render hook
     *
     * Processes rendered output after rendering. Applies WordPress
     * filters for extensibility and ensures all data is properly
     * sanitized. This method should be called at the end of any
     * render method.
     *
     * @param  string  $output   Rendered HTML output
     * @param  array   $scheme   Component scheme data
     * @param  array   $context  Rendering context data
     * @return string            Processed HTML output
     * @access protected
     * @since  1.0.0
     *
     * @see    before_render()
     *
     * @example
     * $final_output = $this->after_render(
     *     '<button>Click me</button>',
     *     ['type' => 'button', 'id' => 'submit-btn'],
     *     ['render_mode' => 'normal']
     * );
     */
    protected function after_render($output, $scheme, $context): string {
        if (!is_string($output) || !is_array($scheme) || !is_array($context)) {
            return '';
        }

        $scheme = wp_parse_args($scheme, self::$default_scheme);
        $context = wp_parse_args($context, self::$default_context);

        $scheme = $this->sanitize_scheme($scheme);
        $context = $this->sanitize_context($context);

        if (has_filter('ultraguard_after_render')) {
            $output = apply_filters('ultraguard_after_render', $output, $scheme, $context);
        }

        $scheme_type = $scheme['type'] ?? '';
        if ($scheme_type && $scheme_type !== 'unknown' && has_filter("ultraguard_after_render_{$scheme_type}")) {
            $output = apply_filters("ultraguard_after_render_{$scheme_type}", $output, $scheme, $context);
        }

        $scheme_id = $scheme['id'] ?? '';
        if ($scheme_id && has_filter("ultraguard_after_render_{$scheme_id}")) {
            $output = apply_filters("ultraguard_after_render_{$scheme_id}", $output, $scheme, $context);
        }

        return $output;
    }

    /**
     * Render wrapper element
     *
     * Generates an HTML wrapper element around content with configurable
     * attributes, classes, and data attributes. Validates wrapper tags
     * against allowed list for security.
     *
     * @param  string  $content  Inner content to wrap
     * @param  array   $scheme   Wrapper configuration scheme
     * @param  array   $context  Rendering context data
     * @return string            Wrapped HTML content
     * @access protected
     * @since  1.0.0
     *
     * @throws \UnexpectedValueException If wrapper tag is not allowed
     *
     * @example
     * $wrapped = $this->render_wrapper(
     *     'Hello World',
     *     [
     *         'id' => 'greeting',
     *         'class' => 'text-large',
     *         'wrapper_tag' => 'section',
     *         'data' => ['toggle' => 'true']
     *     ],
     *     []
     * );
     * // Returns: <section id="greeting" class="ultraguard-component text-large" data-toggle="true">Hello World</section>
     */
    protected function render_wrapper($content, $scheme, $context): string {
        if (!is_string($content) || !is_array($scheme)) {
            return '';
        }

        $scheme = wp_parse_args($scheme, self::$default_wrapper_scheme);
        $scheme = $this->sanitize_scheme($scheme);

        $id = $this->escape($scheme['id'], 'attribute');
        $class = $this->escape($scheme['class'], 'attribute');
        $style = $this->escape($scheme['style'], 'attribute');
        $wrapper_tag = $this->escape($scheme['wrapper_tag'], 'attribute');

        if (!in_array($wrapper_tag, self::$allowed_wrapper_tags, true)) {
            $wrapper_tag = 'div';
        }

        $attributes = [];

        if ($id) {
            $attributes[] = sprintf('id="%s"', $id);
        }

        $classes = ['ultraguard-component'];
        if ($class) {
            $classes[] = $class;
        }

        if (has_filter('ultraguard_wrapper_classes')) {
            $classes = apply_filters('ultraguard_wrapper_classes', $classes, $scheme, $context);
        }

        if (!empty($classes)) {
            $attributes[] = sprintf('class="%s"', implode(' ', $classes));
        }

        if ($style) {
            $attributes[] = sprintf('style="%s"', $style);
        }

        if (!empty($scheme['data']) && is_array($scheme['data'])) {
            foreach ($scheme['data'] as $data_key => $data_value) {
                if (!is_string($data_key) || !$data_key) {
                    continue;
                }

                $data_key = sanitize_key($data_key);

                if (is_array($data_value) || is_object($data_value)) {
                    $data_value = wp_json_encode($data_value);
                } elseif (is_bool($data_value)) {
                    $data_value = $data_value ? 'true' : 'false';
                } elseif (is_numeric($data_value)) {
                    $data_value = (string) $data_value;
                } elseif (!is_string($data_value)) {
                    continue;
                }

                $attributes[] = sprintf(
                    'data-%s="%s"',
                    $this->escape($data_key, 'attribute'),
                    $this->escape($data_value, 'attribute')
                );
            }
        }

        if (!empty($scheme['attributes']) && is_array($scheme['attributes'])) {
            foreach ($scheme['attributes'] as $attr_key => $attr_value) {
                if (!is_string($attr_key) || !$attr_key) {
                    continue;
                }

                $attr_key = sanitize_key($attr_key);

                if (is_bool($attr_value)) {
                    if ($attr_value) {
                        $attributes[] = $this->escape($attr_key, 'attribute');
                    }
                } else {
                    $attributes[] = sprintf(
                        '%s="%s"',
                        $this->escape($attr_key, 'attribute'),
                        $this->escape($attr_value, 'attribute')
                    );
                }
            }
        }

        if (has_filter('ultraguard_wrapper_attributes')) {
            $attributes = apply_filters('ultraguard_wrapper_attributes', $attributes, $scheme, $context);
        }

        $attributes_string = !empty($attributes) ? ' ' . implode(' ', $attributes) : '';

        $output = sprintf('<%s%s>%s</%s>', $wrapper_tag, $attributes_string, $content, $wrapper_tag);

        if (has_filter('ultraguard_wrapper_output')) {
            $output = apply_filters('ultraguard_wrapper_output', $output, $scheme, $context);
        }

        return $output;
    }

    /**
     * Render children components
     *
     * Processes and renders an array of child component schemes. Handles
     * errors gracefully with debug information and applies filters for
     * extensibility. Each child is rendered through the RenderEngine.
     *
     * @param  array  $children  Array of child component schemes
     * @param  array  $context   Rendering context data
     * @return string            Concatenated rendered output of all children
     * @access protected
     * @since  1.0.0
     *
     * @throws \Exception Propagated from child component rendering
     *
     * @example
     * $children_output = $this->render_children(
     *     [
     *         ['type' => 'button', 'label' => 'Save'],
     *         ['type' => 'button', 'label' => 'Cancel']
     *     ],
     *     ['render_mode' => 'normal']
     * );
     */
    protected function render_children($children, $context){
        $output = '';

        if (!is_array($children) || empty($children)) {
            return $output;
        }

        $context = wp_parse_args($context, self::$default_context);
        $context = $this->sanitize_context($context);

        if (has_filter('ultraguard_before_render_children')) {
            $children = apply_filters('ultraguard_before_render_children', $children, $context);
        }

        foreach ($children as $index => $child_scheme) {
            if (!is_array($child_scheme)) {
                continue;
            }

            $child_scheme = $this->sanitize_scheme($child_scheme);

            try {
                $child_output = $this->engine->render($child_scheme, $context);
                $output .= $child_output;
            } catch (\Exception $e) {
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    $output .= $this->handle_render_error(
                        sprintf('Child component %d: %s', $index, $e->getMessage()),
                        $child_scheme
                    );
                }

                if (function_exists('error_log')) {
                    \UltraguardPro\core\Logger::error(
                        sprintf(
                        'Ultraguard Render Error: %s - Component: %s',
                        $e->getMessage(),
                        $child_scheme['type'] ?? 'unknown'
                    )
                    );
                }
            }
        }

        if (has_filter('ultraguard_after_render_children')) {
            $output = apply_filters('ultraguard_after_render_children', $output, $children, $context);
        }

        return $output;
    }

    /**
     * Escape output based on context
     *
     * Applies appropriate escaping based on output context (HTML, attribute,
     * URL, etc.). Handles various data types including arrays, objects,
     * booleans, and numbers. Uses WordPress escaping functions for security.
     *
     * @param  mixed   $value    Value to escape
     * @param  string  $context  Escape context ('html', 'attribute', 'url', 'textarea', 'js')
     * @return string            Escaped value as string
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $escaped = $this->escape('<script>alert("xss")</script>', 'html');
     * // Returns: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;
     *
     * $escaped = $this->escape('user input', 'attribute');
     * // Returns: user input (escaped for HTML attribute context)
     */
    protected function escape($value, $context = 'html'){
        if (is_null($value)) {
            return '';
        }

        // Handle arrays and objects
        if (is_array($value) || is_object($value)) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                $json_value = wp_json_encode($value);
                return $json_value ? $this->escape($json_value, $context) : '[' . gettype($value) . ']';
            }
            return '';
        }

        // Handle booleans
        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        // Handle numbers
        if (is_numeric($value)) {
            $value = (string) $value;
        }

        // Ensure value is string for escaping
        if (!is_string($value)) {
            return '';
        }

        switch ($context) {
            case 'attribute':
                return esc_attr($value);
            case 'url':
                return esc_url($value);
            case 'textarea':
                return esc_textarea($value);
            case 'js':
                return esc_js($value);
            case 'html':
            default:
                return wp_kses_post( is_array( $value ) ? esc_html( wp_json_encode( $value ) ) : (string)( $value ) );
        }
    }

    /**
     * Sanitize HTML content (allow some safe HTML)
     *
     * Filters HTML content to allow only safe HTML tags and attributes.
     * Uses WordPress kses functions with a default allowed HTML list
     * that can be overridden. Prevents XSS attacks through user content.
     *
     * @param  string       $content       Content to sanitize
     * @param  array|null   $allowed_html  Custom allowed HTML configuration
     * @return string                      Sanitized HTML content
     * @access protected
     * @since  1.0.0
     *
     * @see    wp_kses()
     *
     * @example
     * $clean = $this->sanitize('<script>alert("xss")</script><p>Safe text</p>');
     * // Returns: <p>Safe text</p>
     */
    protected function sanitize(string $content, $allowed_html = null): string {
        if (!$content) {
            return '';
        }

        if ($allowed_html === null) {
            static $default_allowed_html = null;

            if ($default_allowed_html === null) {
                $default_allowed_html = [
                    'div' => [
                        'class' => true,
                        'id' => true,
                        'style' => true,
                        'data-*' => true,
                    ],
                    'span' => [
                        'class' => true,
                        'id' => true,
                        'style' => true,
                    ],
                    'p' => ['class' => true],
                    'br' => [],
                    'strong' => [],
                    'em' => [],
                    'b' => [],
                    'i' => [],
                    'ul' => ['class' => true],
                    'ol' => ['class' => true],
                    'li' => ['class' => true],
                    'a' => [
                        'href' => true,
                        'title' => true,
                        'target' => true,
                        'class' => true,
                        'rel' => true,
                    ],
                    'img' => [
                        'src' => true,
                        'alt' => true,
                        'title' => true,
                        'class' => true,
                        'width' => true,
                        'height' => true,
                    ],
                    'table' => [
                        'class' => true,
                        'border' => true,
                    ],
                    'thead' => [],
                    'tbody' => [],
                    'tr' => [],
                    'th' => ['scope' => true],
                    'td' => [],
                    'h1' => ['class' => true],
                    'h2' => ['class' => true],
                    'h3' => ['class' => true],
                    'h4' => ['class' => true],
                    'h5' => ['class' => true],
                    'h6' => ['class' => true],
                    'canvas' => [
                        'id'    => true,
                        'class' => true,
                        'style' => true,
                        'width' => true,
                        'height' => true,
                        'data-config' => true,
                    ],
                ];
            }

            $allowed_html = $default_allowed_html;
        }

        return wp_kses($content, $allowed_html);
    }

    /**
     * Handle render errors
     *
     * Generates user-friendly error messages when rendering fails. In debug
     * mode, displays detailed error information. In production, returns
     * empty string to fail gracefully.
     *
     * @param  string  $error_message  Error description
     * @param  array   $scheme         Render scheme that caused the error
     * @return string                  Error HTML or empty string
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $error_html = $this->handle_render_error(
     *     'Invalid component type',
     *     ['type' => 'invalid_type', 'id' => 'test']
     * );
     */
    protected function handle_render_error(string $error_message, array $scheme): string {
        if (!$error_message) {
            $error_message = esc_html__('Unknown rendering error', 'ultraguard-security-suite');
        }

        $scheme = wp_parse_args($scheme, self::$default_error_scheme);
        $scheme = $this->sanitize_scheme($scheme);

        if (defined('WP_DEBUG') && WP_DEBUG) {
            return sprintf(
                '<div class="ultraguard-render-error" style="border: 1px solid #dc3232; padding: 10px; margin: 10px 0; background: #ffeaea;">' .
                '<strong>%s</strong>: %s<br>' .
                '<small>%s: %s</small>' .
                '</div>',
                esc_html__('Rendering Error', 'ultraguard-security-suite'),
                esc_html($error_message),
                esc_html__('Component', 'ultraguard-security-suite'),
                esc_html($scheme['type'] . (!empty($scheme['id']) ? ' (' . $scheme['id'] . ')' : ''))
            );
        }

        return '';
    }

    /**
     * Generate a unique field ID from name
     *
     * Creates a sanitized, unique HTML ID attribute from a field name.
     * Uses caching to ensure consistent ID generation for the same name.
     * Falls back to a unique ID if name cannot be sanitized.
     *
     * @param  string  $name  Field name to convert to ID
     * @return string         Generated HTML ID attribute
     * @access protected
     * @since  1.0.0
     *
     * @example
     * $id = $this->generate_field_id('user_email');
     * // Returns: user_email
     *
     * $id = $this->generate_field_id('User Email!');
     * // Returns: user_email_
     */
    protected function generate_field_id(string $name, $entity_id = null, $context = null) {
        if (!$name) {
            return 'ultraguard_field_' . uniqid();
        }

        // Build cache key including entity_id and context
        $cache_key = 'field_id_' . md5($name . '_' . $entity_id . '_' . $context);
        if (isset(self::$sanitization_cache[$cache_key])) {
            return self::$sanitization_cache[$cache_key];
        }

        // Build deterministic ID
        $parts = ['ultraguard'];

        if ($context) {
            $parts[] = sanitize_html_class($context);
        }

        $parts[] = sanitize_html_class($name);

        if ($entity_id) {
            $parts[] = $entity_id;
        }

        $id = implode('_', $parts);
        $id = preg_replace('/[^a-zA-Z0-9_-]/', '_', $id);
        $id = trim($id, '_');

        self::$sanitization_cache[$cache_key] = $id;
        return $id;
    }

    /**
     * Check if current user has capability to render component
     *
     * Verifies user permissions based on capability, roles, and specific
     * user IDs defined in the scheme. Integrates with WordPress user
     * capabilities system and provides filter for custom logic.
     *
     * @param  array  $scheme   Component scheme with capability settings
     * @param  array  $context  Rendering context data
     * @return bool             True if user can render, false otherwise
     * @access protected
     * @since  1.0.0
     *
     * @see    current_user_can()
     * @see    wp_get_current_user()
     *
     * @example
     * $can_render = $this->user_can_render(
     *     ['capability' => 'edit_posts', 'roles' => ['editor']],
     *     []
     * );
     */
    protected function user_can_render($scheme, $context): bool {
        if (!is_array($scheme) || !is_array($context)) {
            return false;
        }

        $scheme = wp_parse_args($scheme, self::$default_capability_scheme);
        $context = wp_parse_args($context, self::$default_context);

        $scheme = $this->sanitize_scheme($scheme);
        $context = $this->sanitize_context($context);

        if (!current_user_can($scheme['capability'])) {
            return false;
        }

        if (!empty($scheme['roles']) && is_array($scheme['roles'])) {
            $user = wp_get_current_user();
            $has_role = false;

            foreach ($scheme['roles'] as $role) {
                if (in_array($role, (array) $user->roles, true)) {
                    $has_role = true;
                    break;
                }
            }

            if (!$has_role) {
                return false;
            }
        }

        if (!empty($scheme['users']) && is_array($scheme['users'])) {
            $current_user_id = get_current_user_id();
            if (!in_array($current_user_id, $scheme['users'], true)) {
                return false;
            }
        }

        if (has_filter('ultraguard_user_can_render')) {
            return (bool) apply_filters('ultraguard_user_can_render', true, $scheme, $context);
        }

        return true;
    }

    /**
     * Sanitize scheme data
     *
     * Recursively sanitizes scheme data using WordPress sanitization
     * functions. Uses caching to avoid redundant processing of identical
     * scheme data. Ensures all scheme data is safe for use in rendering.
     *
     * @param  array  $scheme  Scheme data to sanitize
     * @return array           Sanitized scheme data
     * @access protected
     * @since  1.0.0
     *
     * @see    sanitize_key()
     * @see    sanitize_text_field()
     *
     * @example
     * $clean_scheme = $this->sanitize_scheme([
     *     'type' => '<script>alert("xss")</script>',
     *     'data' => ['key' => 'value']
     * ]);
     * // Returns: ['type' => 'alert("xss")', 'data' => ['key' => 'value']]
     */
    protected function sanitize_scheme(array $scheme): array {
        if (empty($scheme)) {
            return [];
        }

        $cache_key = 'scheme_' . md5(serialize($scheme));
        if (isset(self::$sanitization_cache[$cache_key])) {
            return self::$sanitization_cache[$cache_key];
        }

        $sanitized = [];

        foreach ($scheme as $key => $value) {
            $sanitized_key = sanitize_key($key);

            // Keys that hold safe HTML — use wp_kses_post instead of stripping tags
            static $html_keys = [ 'html', 'content', 'message', 'description', 'footer' ];

            if (is_array($value)) {
                $sanitized[$sanitized_key] = $this->sanitize_scheme($value);
            } elseif (is_string($value) && in_array($sanitized_key, $html_keys, true)) {
                $sanitized[$sanitized_key] = wp_kses_post($value);
            } elseif (is_string($value)) {
                $sanitized[$sanitized_key] = sanitize_text_field($value);
            } else {
                $sanitized[$sanitized_key] = $value;
            }
        }

        self::$sanitization_cache[$cache_key] = $sanitized;
        return $sanitized;
    }

    /**
     * Sanitize context data
     *
     * Recursively sanitizes context data using WordPress sanitization
     * functions. Uses caching for performance and ensures all context
     * data is safe for use in rendering logic.
     *
     * @param  array  $context  Context data to sanitize
     * @return array            Sanitized context data
     * @access protected
     * @since  1.0.0
     *
     * @see    sanitize_scheme()
     *
     * @example
     * $clean_context = $this->sanitize_context([
     *     'render_mode' => 'preview<script>',
     *     'settings' => ['debug' => true]
     * ]);
     * // Returns: ['render_mode' => 'preview', 'settings' => ['debug' => true]]
     */
    protected function sanitize_context(array $context): array {
        if (empty($context)) {
            return [];
        }

        $cache_key = 'context_' . md5(serialize($context));
        if (isset(self::$sanitization_cache[$cache_key])) {
            return self::$sanitization_cache[$cache_key];
        }

        $sanitized = [];

        foreach ($context as $key => $value) {
            $sanitized_key = sanitize_key($key);

            if (is_array($value)) {
                $sanitized[$sanitized_key] = $this->sanitize_context($value);
            } elseif (is_string($value)) {
                $sanitized[$sanitized_key] = sanitize_text_field($value);
            } else {
                $sanitized[$sanitized_key] = $value;
            }
        }

        self::$sanitization_cache[$cache_key] = $sanitized;
        return $sanitized;
    }
}
