<?php
namespace UltraguardPro\core;

use UltraguardPro\view\RenderEngine;
use UltraguardPro\support\LicenseInfo;

if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Main Admin Class with Unified Page Registration System
 *
 * This class manages the entire Ultraguard Pro admin interface using a unified
 * page registration system with a custom sidebar navigation that replaces
 * default WordPress submenus. It implements a singleton pattern and provides
 * a modular app-style layout for all admin pages.
 *
 * @package    UltraguardPro\core
 * @author     Vikas Bhardwaj
 * @version    1.0.0
 * @since      1.0.0
 * @see        \UltraguardPro\view\RenderEngine
 */
class Admin{

    /**
     * Instance for singleton pattern
     *
     * @var Admin|null
     * @access private
     * @static
     */
    private static $instance = null;

    /**
     * Plugin instance reference
     *
     * @var \UltraguardPro\Plugin|null
     * @access private
     */
    private $plugin = null;

    /**
     * Render engine instance
     *
     * @var \UltraguardPro\view\RenderEngine|null
     * @access private
     */
    private $render_engine = null;

    /**
     * Registered admin pages
     *
     * Stores all registered page configurations with page ID as key.
     *
     * @var array
     * @access private
     */
    private $pages = [];

    /**
     * Cached page hierarchy
     *
     * Hierarchical structure of pages with parent-child relationships.
     *
     * @var array|null
     * @access private
     */
    private $page_hierarchy_cache = null;

    /**
     * Cached child pages by parent
     *
     * Quick lookup cache for child pages grouped by parent ID.
     *
     * @var array
     * @access private
     */
    private $child_pages_cache = [];

    /**
     * Cached main page config
     *
     * Cached configuration of the main (first) admin page.
     *
     * @var array|null
     * @access private
     */
    private $main_page_config_cache = null;

    /**
     * Main parent menu slug
     *
     * WordPress menu slug for the main parent menu item.
     *
     * @var string
     * @access private
     */
    private $parent_menu_slug = 'ultraguard-security-suite';

    /**
     * Logo configuration
     *
     * Configuration for the sidebar logo display.
     *
     * @var array
     * @access private
     */
    private $logo_config = [];

    /**
     * Whether components are initialized
     *
     * Flag to prevent multiple initializations.
     *
     * @var bool
     * @access private
     */
    private $components_initialized = false;

    /**
     * Admin page hook for current request
     *
     * WordPress admin page hook for the current request.
     *
     * @var string|null
     * @access private
     */
    private $current_admin_hook = null;

    /**
     * License info instance
     *
     * @var \UltraguardPro\support\LicenseInfo|null
     * @access private
     */
    private $license = null;

    /**
     * Whether to show free plan footer notice
     *
     * @var bool
     * @access private
     */
    private $show_free_footer = true;

    /**
     * Free plan footer notice dismissed transient
     *
     * @var string
     * @access private
     */
    private const FOOTER_DISMISS_TRANSIENT = 'ultraguard_free_footer_dismissed';

    /**
     * Free plan footer notice dismissal duration (7 days)
     *
     * @var int
     */
    private const FOOTER_DISMISS_DURATION = 604800;

    /**
     * Get singleton instance
     *
     * Returns the singleton instance of the Admin class.
     * Implements lazy initialization pattern.
     *
     * @param  \UltraguardPro\Plugin|null $plugin Plugin instance reference
     * @return Admin                      Singleton instance
     * @access public
     * @static
     * @since  1.0.0
     *
     * @example
     * $admin = Admin::get_instance($plugin);
     */
    public static function get_instance($plugin) {
        if ( null === self::$instance ) {
            self::$instance = new self($plugin);
        }
        return self::$instance;
    }

    /**
     * Constructor - Private to prevent direct instantiation
     *
     * Sets up action hooks for admin menu registration and asset loading.
     *
     * @param \UltraguardPro\Plugin|null $plugin Plugin instance reference
     * @access private
     * @since  1.0.0
     */
    private function __construct($plugin = null) {
        $this->plugin = $plugin;

        add_action( 'admin_menu', [ $this, 'admin_menu_early' ], 5 );
        add_action( 'admin_menu', [ $this, 'admin_menu_late' ], 10 );
        // Priority 1: register shared handles on EVERY admin page so that
        // module scripts (e.g. filechecker-page) can safely declare them as
        // dependencies regardless of which page is currently active.
        add_action( 'admin_enqueue_scripts', [ $this, 'register_shared_scripts' ], 1 );
        add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_assets' ] );
        add_action( 'wp_ajax_ultraguard_dismiss_free_footer', [ $this, 'ajax_dismiss_free_footer' ] );
        add_action( 'admin_notices', [ $this, 'maybe_show_free_footer_notice' ] );
        add_filter( 'admin_body_class', [ $this, 'add_admin_body_class' ] );
    }

    /**
     * Early admin menu hook for page registration
     *
     * Initializes components and fires action for external page registration.
     * Runs before WordPress processes admin menu items.
     *
     * @return void
     * @access public
     * @since  1.0.0
     *
     * @see    init_components()
     * @see    ultraguard_register_admin_pages action
     */
    public function admin_menu_early() {
        $this->init_components();
        do_action( 'ultraguard_register_admin_pages', $this );
    }

    /**
     * Late admin menu hook for page registration
     *
     * Registers admin pages with WordPress after all pages have been
     * registered by external components.
     *
     * @return void
     * @access public
     * @since  1.0.0
     *
     * @see    register_admin_pages()
     */
    public function admin_menu_late() {
        $this->register_admin_pages();
    }

    /**
     * Initialize components only when needed
     *
     * Lazy initialization of render engine, logo config, and default renderers.
     * Ensures components are only initialized once.
     *
     * @return void
     * @access private
     * @since  1.0.0
     *
     * @see    init_logo_config()
     * @see    register_default_renderers()
     */
    private function init_components() {
        if ( $this->components_initialized ) {
            return;
        }

        $this->render_engine = RenderEngine::get_instance();
        $this->init_logo_config();
        $this->register_default_renderers();

        $this->components_initialized = true;
    }

    /**
     * Initialize logo configuration with license awareness
     *
     * Sets up default logo configuration and allows filtering via
     * 'ultraguard_logo_config' filter. Shows plan via secondary text.
     *
     * @return void
     * @access private
     * @since  1.0.0
     *
     * @see    ultraguard_logo_config filter
     */
    private function init_logo_config() {
        $is_pro = $this->get_license()->isPro();

        $default_logo = [
            'type'           => 'icon',
            'icon'           => 'fa-solid fa-shield-halved',
            'image_url'      => '',
            'primary_text'   => 'UltraGuard',
            'secondary_text' => $is_pro ? 'Pro' : 'Free',
            'show_text'      => true,
        ];

        $this->logo_config = apply_filters( 'ultraguard_logo_config', $default_logo );
    }

    /**
     * Register default renderers
     *
     * Fires action to allow external registration of renderers with
     * the render engine.
     *
     * @return void
     * @access private
     * @since  1.0.0
     *
     * @see    ultraguard_register_renderers action
     */
    private function register_default_renderers() {
        do_action( 'ultraguard_register_renderers', $this->render_engine );
    }

    /**
     * Register a new admin page
     *
     * Adds a page configuration to the internal registry with validation
     * and default values. Clears caches after registration.
     *
     * @param  array $config Page configuration array
     * @return bool          True on success, false on failure
     * @access public
     * @since  1.0.0
     */
    public function register_page( $config ) {
        // SECURE: Ensure config is always an array
        $config = is_array( $config ) ? $config : [];

        // Validate required fields with safe defaults
        $required_fields = [ 'id', 'page_title', 'menu_title', 'scheme_callback' ];
        foreach ( $required_fields as $field ) {
            if ( empty( $config[ $field ] ) ) {
                _doing_it_wrong(
                    __METHOD__,
                    sprintf(
                        /* translators: %s: field name */
                        esc_html__( 'Missing required field: %s', 'ultraguard-security-suite' ),
                        esc_html( $field )
                    ),
                    '1.0.0'
                );
                return false;
            }
        }

        $page_id = $config['id'] ?? '';

        // Check if page ID already exists
        if ( isset( $this->pages[ $page_id ] ) ) {
            _doing_it_wrong(
                __METHOD__,
                sprintf(
                    /* translators: %s: page ID */
                    esc_html__( 'Page with ID "%s" is already registered', 'ultraguard-security-suite' ),
                    esc_html( $page_id )
                ),
                '1.0.0'
            );
            return false;
        }

        // Set comprehensive defaults using wp_parse_args
        $defaults = [
            'id'                => '',
            'page_title'        => '',
            'menu_title'        => '',
            'scheme_callback'   => null,
            'capability'        => 'manage_options',
            'is_parent'         => false,
            'icon'              => 'dashicons-admin-generic',
            'position'          => null,
            'priority'          => 10,
            'group'             => '',
            'parent_id'         => '',
            'is_hidden'         => false,
            'menu_slug'         => '',
            'parent_slug'       => '',
            'show_free_footer'  => true, // Per-page control for free footer
        ];

        $config = wp_parse_args( $config, $defaults );
        $config['icon'] = $this->normalize_registered_page_icon( $page_id, (string) $config['icon'] );
        $config = apply_filters( 'ultraguard_registered_page_config', $config, $page_id );

        if ( is_network_admin() && 'manage_options' === $config['capability'] ) {
            $config['capability'] = 'manage_network_options';
        }

        // Generate menu slug if not provided
        if ( empty( $config['menu_slug'] ) ) {
            $config['menu_slug'] = 'ultraguard-security-suite-' . $config['id'];
        }

        if ( $config['is_parent'] && $this->parent_menu_slug === 'ultraguard-security-suite' ) {
            $this->parent_menu_slug = $config['menu_slug'];
        }

        if ( ! $config['is_parent'] ) {
            $config['parent_slug'] = $this->parent_menu_slug;
        }

        $cb = $config['scheme_callback'];
        $is_lazy_format = (
            is_array( $cb )
            && isset( $cb[0], $cb[1] )
            && is_string( $cb[0] )
            && class_exists( $cb[0] )
            && is_object( $cb[1] )
        );

        if ( ! $is_lazy_format && ! is_callable( $cb ) ) {
            _doing_it_wrong(
                __METHOD__,
                esc_html__( 'Scheme callback must be a valid callable', 'ultraguard-security-suite' ),
                '1.0.0'
            );
            return false;
        }

        // Store page configuration and clear caches
        $this->pages[ $page_id ] = $config;
        $this->page_hierarchy_cache = null;
        $this->main_page_config_cache = null;
        $this->child_pages_cache = [];

        /**
         * Action after a page is registered
         *
         * @param array  $config  Page configuration
         * @param string $page_id Page ID
         */
        do_action( 'ultraguard_page_registered', $config, $page_id );

        return true;
    }

    /**
     * Normalize page icons so sidebar/page navigation can use a curated set
     * of relevant icons while still allowing modules to override them.
     *
     * @param string $page_id Page identifier.
     * @param string $icon    Registered icon class.
     * @return string
     */
    private function normalize_registered_page_icon( string $page_id, string $icon ): string {
        $icon_map = [
            'dashboard'                  => 'fa-solid fa-gauge-high',
            'modules-manager'            => 'fa-solid fa-cubes-stacked',
            'antivirus'                  => 'fa-solid fa-shield-virus',
            'file-scanner'               => 'fa-solid fa-folder-tree',
            'malware-detection'          => 'fa-solid fa-bug',
            'quarantine'                 => 'fa-solid fa-box-archive',
            'scanner-settings'           => 'fa-solid fa-sliders',
            'api-security'               => 'fa-solid fa-plug',
            'api-security-settings'      => 'fa-solid fa-sliders',
            'audit-log'                  => 'fa-solid fa-clipboard-list',
            'authenticator'              => 'fa-solid fa-user-shield',
            'two-factor'                 => 'fa-solid fa-user-shield',
            'two-factor-settings'        => 'fa-solid fa-sliders',
            'passkeys'                   => 'fa-solid fa-key',
            'passkey-setting'            => 'fa-solid fa-key',
            'auto-updater'               => 'fa-solid fa-rotate',
            'auto-updater-settings'      => 'fa-solid fa-sliders',
            'compliance-reports'         => 'fa-solid fa-file-lines',
            'compliance-reports-settings'=> 'fa-solid fa-sliders',
            'database-scanner'           => 'fa-solid fa-database',
            'filechecker'                => 'fa-solid fa-file-code',
            'filechecker-monitoring'     => 'fa-solid fa-binoculars',
            'filechecker-changes'        => 'fa-solid fa-arrows-rotate',
            'filechecker-quarantine'     => 'fa-solid fa-box-archive',
            'filechecker-history'        => 'fa-solid fa-clock-rotate-left',
            'filechecker-settings'       => 'fa-solid fa-sliders',
            'firewall'                   => 'fa-solid fa-fire',
            'firewall-rules'             => 'fa-solid fa-list-check',
            'firewall-logs'              => 'fa-solid fa-scroll',
            'firewall-ip-management'     => 'fa-solid fa-ban',
            'firewall-country-blocking'  => 'fa-solid fa-earth-asia',
            'firewall-add-rules'         => 'fa-solid fa-plus',
            'firewall-settings'          => 'fa-solid fa-sliders',
            'hardening'                  => 'fa-solid fa-screwdriver-wrench',
            'htaccess'                   => 'fa-solid fa-file-code',
            'htaccess-security-rules'    => 'fa-solid fa-shield-halved',
            'htaccess-backups'           => 'fa-solid fa-box-archive',
            'htaccess-security-events'   => 'fa-solid fa-triangle-exclamation',
            'htaccess-custom-rules'      => 'fa-solid fa-code',
            'htaccess-change-log'        => 'fa-solid fa-clock-rotate-left',
            'htaccess-settings'          => 'fa-solid fa-sliders',
            'ip-intelligence'            => 'fa-solid fa-globe',
            'ip-intelligence-settings'   => 'fa-solid fa-sliders',
            'login-limiter'              => 'fa-solid fa-lock',
            'login-attempts'             => 'fa-solid fa-right-to-bracket',
            'ip-management'              => 'fa-solid fa-network-wired',
            'lockouts'                   => 'fa-solid fa-user-lock',
            'login-limiter-settings'     => 'fa-solid fa-sliders',
            'login-protect'              => 'fa-solid fa-link-slash',
            'login-protect-settings'     => 'fa-solid fa-sliders',
            'notifications'              => 'fa-solid fa-bell',
            'notifications-rules'        => 'fa-solid fa-filter',
            'notifications-channels'     => 'fa-solid fa-paper-plane',
            'notifications-log'          => 'fa-solid fa-inbox',
            'notifications-statistics'   => 'fa-solid fa-chart-column',
            'notifications-settings'     => 'fa-solid fa-sliders',
            'reputation-monitor'         => 'fa-solid fa-magnifying-glass',
            'reputation-monitor-settings'=> 'fa-solid fa-sliders',
            'security-headers'           => 'fa-solid fa-heading',
            'server-intelligence'        => 'fa-solid fa-cloud-arrow-down',
            'server-intelligence-settings'=> 'fa-solid fa-sliders',
            'traffic-monitor'            => 'fa-solid fa-chart-line',
            'traffic-monitor-settings'   => 'fa-solid fa-sliders',
            'upgrade-manager'            => 'fa-solid fa-crown',
            'upgrade-manager-free'       => 'fa-solid fa-key',
            'upgrade-manager-paid'       => 'fa-solid fa-credit-card',
            'upgrade-manager-settings'   => 'fa-solid fa-sliders',
            'upgrade'                    => 'fa-solid fa-crown',
            'uptime-monitor'             => 'fa-solid fa-heart-pulse',
            'uptime-monitor-settings'    => 'fa-solid fa-sliders',
            'vulnerability-scanner'      => 'fa-solid fa-bug',
            'vulnerability-results'      => 'fa-solid fa-list-check',
            'virtual-patches'            => 'fa-solid fa-bandage',
            'woo-security'               => 'fa-solid fa-cart-shopping',
            'woo-security-settings'      => 'fa-solid fa-sliders',
        ];

        $icon_map = apply_filters( 'ultraguard_page_icon_map', $icon_map );

        return $icon_map[ $page_id ] ?? $icon;
    }

    /**
     * Render an icon for sidebar/logo/page navigation.
     *
     * Supports Dashicons and Font Awesome class strings.
     *
     * @param string $icon_class  Icon class string.
     * @param string $extra_class Extra wrapper classes.
     * @return string
     */
    private function render_icon_html( string $icon_class, string $extra_class = '' ): string {
        $icon_class  = trim( $icon_class );
        $extra_class = trim( $extra_class );
        $classes     = trim( 'ultraguard-icon ' . $extra_class );

        if ( '' === $icon_class ) {
            $icon_class = 'dashicons-admin-generic';
        }

        if ( str_contains( $icon_class, 'fa-' ) ) {
            return sprintf(
                '<span class="%s" aria-hidden="true"><i class="%s" aria-hidden="true"></i></span>',
                esc_attr( $classes ),
                esc_attr( $icon_class )
            );
        }

        return sprintf(
            '<span class="%s dashicons %s" aria-hidden="true"></span>',
            esc_attr( $classes ),
            esc_attr( $icon_class )
        );
    }

    /**
     * Allowed icon markup returned by render_icon_html().
     *
     * @return array<string, array<string, bool>>
     */
    private function get_allowed_icon_html(): array {
        return [
            'span' => [
                'class'       => true,
                'aria-hidden' => true,
            ],
            'i'    => [
                'class'       => true,
                'aria-hidden' => true,
            ],
        ];
    }

    /**
     * WordPress native admin menu only supports Dashicons or image URLs.
     *
     * @param string $icon_class Registered page icon.
     * @return string
     */
    private function get_wordpress_menu_icon( string $icon_class ): string {
        if ( str_contains( $icon_class, 'fa-' ) ) {
            return 'dashicons-shield-alt';
        }

        return $icon_class ?: 'dashicons-shield-alt';
    }

    /**
     * Build navigation hierarchy with parent page as first submenu item
     *
     * Creates a hierarchical structure of pages for sidebar navigation.
     * Includes parent pages as first child items when they have children.
     * Uses caching for performance.
     *
     * @return array Hierarchical pages structure with 'page' and 'children' keys
     * @access private
     * @since  1.0.0
     */
    private function build_page_hierarchy() {
        // Return cached hierarchy if available
        if ( null !== $this->page_hierarchy_cache ) {
            return $this->page_hierarchy_cache;
        }

        $hierarchy = [];
        $pages = $this->pages;

        foreach ( $pages as $page_id => $page_config ) {
            $parent_id = $page_config['parent_id'] ?? '';

            if ( empty( $parent_id ) ) {
                // This is a top-level page
                $hierarchy[ $page_id ] = [
                    'page'     => $page_config,
                    'children' => []
                ];

                // If this parent page has children, add the parent itself as first child
                if ( $this->has_children( $page_id ) ) {
                    $parent_as_child = $page_config;
                    $parent_as_child['menu_title'] = __( 'Dashboard', 'ultraguard-security-suite' );
                    $parent_as_child['is_parent_overview'] = true;
                    $hierarchy[ $page_id ]['children'][ $page_id ] = $parent_as_child;
                }
            } else {
                // This is a child page
                if ( ! isset( $hierarchy[ $parent_id ] ) ) {
                    $parent_page = $pages[ $parent_id ] ?? null;
                    if ( $parent_page ) {
                        $hierarchy[ $parent_id ] = [
                            'page'     => $parent_page,
                            'children' => []
                        ];

                        // Add parent as first child if it has children
                        if ( $this->has_children( $parent_id ) ) {
                            $parent_as_child = $parent_page;
                            $parent_as_child['menu_title'] = ( $parent_as_child['menu_title'] ?? '' ) . ' ' . __( 'Overview', 'ultraguard-security-suite' );
                            $parent_as_child['is_parent_overview'] = true;
                            $hierarchy[ $parent_id ]['children'][ $parent_id ] = $parent_as_child;
                        }
                    }
                }

                if ( isset( $hierarchy[ $parent_id ] ) ) {
                    $hierarchy[ $parent_id ]['children'][ $page_id ] = $page_config;
                }
            }
        }

        // Cache the result
        $this->page_hierarchy_cache = $hierarchy;

        return $hierarchy;
    }

    /**
     * Get all child pages for a parent
     *
     * Returns child pages for a given parent ID, sorted by priority.
     * Uses caching for performance.
     *
     * @param  string $parent_id Parent page ID
     * @return array             Child pages array with page IDs as keys
     * @access private
     * @since  1.0.0
     */
    private function get_child_pages( $parent_id ) {
        // Validate parameter
        if ( ! is_string( $parent_id ) || empty( $parent_id ) ) {
            return [];
        }

        // Return cached result if available
        if ( isset( $this->child_pages_cache[ $parent_id ] ) ) {
            return $this->child_pages_cache[ $parent_id ];
        }

        $children = [];
        $pages = $this->pages;

        foreach ( $pages as $page_id => $page_config ) {
            $page_parent_id = $page_config['parent_id'] ?? '';
            if ( $page_parent_id === $parent_id ) {
                $children[ $page_id ] = $page_config;
            }
        }

        // Sort children by priority
        uasort( $children, [ $this, 'sort_by_priority' ] );

        // Cache the result
        $this->child_pages_cache[ $parent_id ] = $children;

        return $children;
    }

    /**
     * Sort callback for priority comparison
     *
     * Compares two page configurations by their priority values.
     * Used with uasort() for sorting arrays.
     *
     * @param  array $a First page configuration
     * @param  array $b Second page configuration
     * @return int      Negative if $a < $b, positive if $a > $b, 0 if equal
     * @access private
     * @since  1.0.0
     */
    private function sort_by_priority( $a, $b ) {
        $priority_a = $a['priority'] ?? 10;
        $priority_b = $b['priority'] ?? 10;
        return $priority_a - $priority_b;
    }

    /**
     * Sort callback for child pages
     *
     * @param array $a First child
     * @param array $b Second child
     * @return int Comparison result
     */
    private function sort_child_pages( $a, $b ) {
        $a_is_overview = ! empty( $a['is_parent_overview'] );
        $b_is_overview = ! empty( $b['is_parent_overview'] );

        if ( $a_is_overview && ! $b_is_overview ) {
            return -1;
        } elseif ( ! $a_is_overview && $b_is_overview ) {
            return 1;
        }

        $priority_a = $a['priority'] ?? 10;
        $priority_b = $b['priority'] ?? 10;
        return $priority_a - $priority_b;
    }

    /**
     * Check if page has children
     *
     * Determines whether a page has any child pages.
     *
     * @param  string $page_id Page ID to check
     * @return bool           True if page has children, false otherwise
     * @access private
     * @since  1.0.0
     */
    private function has_children( $page_id ) {
        if ( ! is_string( $page_id ) || empty( $page_id ) ) {
            return false;
        }
        return ! empty( $this->get_child_pages( $page_id ) );
    }

    /**
     * Get the main page configuration (first parent, or first page)
     *
     * Returns the configuration for the main admin page, which serves
     * as the parent for all other pages. Uses caching for performance.
     *
     * @return array Main page configuration with all required fields
     * @access private
     * @since  1.0.0
     */
    private function get_main_page_config() {
        // Return cached config if available
        if ( null !== $this->main_page_config_cache ) {
            return $this->main_page_config_cache;
        }

        $pages = $this->pages;

        if ( empty( $pages ) ) {
            // Comprehensive failsafe defaults
            $config = [
                'id'               => 'default',
                'page_title'       => 'Dashboard',
                'menu_title'       => 'Dashboard',
                'capability'       => 'manage_options',
                'menu_slug'        => $this->parent_menu_slug,
                'icon'             => 'dashicons-shield',
                'position'         => 2,
                'is_parent'        => true,
                'priority'         => 10,
                'group'            => '',
                'parent_id'        => '',
                'is_hidden'        => false,
                'show_free_footer' => true,
                'scheme_callback'  => function() { return []; }
            ];

            $this->main_page_config_cache = $config;
            return $config;
        }

        // Sort pages to respect priority
        uasort( $pages, [ $this, 'sort_main_pages' ] );

        // Try to find the first 'is_parent'
        foreach ( $pages as $page ) {
            if ( ! empty( $page['is_parent'] ) ) {
                $this->parent_menu_slug = $page['menu_slug'] ?? $this->parent_menu_slug;
                $this->main_page_config_cache = $page;
                return $page;
            }
        }

        // Fallback to first registered page
        $first_page = reset( $pages );
        if ( $first_page ) {
            $this->parent_menu_slug = $first_page['menu_slug'] ?? $this->parent_menu_slug;
            $this->main_page_config_cache = $first_page;
            return $first_page;
        }

        // This should never be reached due to the earlier empty check
        $config = [
            'id'               => 'default',
            'page_title'       => 'Dashboard',
            'menu_title'       => 'Dashboard',
            'capability'       => 'manage_options',
            'menu_slug'        => $this->parent_menu_slug,
            'icon'             => 'dashicons-shield',
            'position'         => 2,
            'is_parent'        => true,
            'priority'         => 10,
            'group'            => '',
            'parent_id'        => '',
            'is_hidden'        => false,
            'show_free_footer' => true,
            'scheme_callback'  => function() { return []; }
        ];

        $this->main_page_config_cache = $config;
        return $config;
    }

    /**
     * Sort callback for main pages
     *
     * Sorts pages giving priority to parent pages, then by priority value.
     * Used to determine which page should be the main admin page.
     *
     * @param  array $a First page configuration
     * @param  array $b Second page configuration
     * @return int      Comparison result for sorting
     * @access private
     * @since  1.0.0
     */
    private function sort_main_pages( $a, $b ) {
        $priority_a = ! empty( $a['is_parent'] ) ? -1 : 1;
        $priority_b = ! empty( $b['is_parent'] ) ? -1 : 1;

        if ( $priority_a !== $priority_b ) {
            return $priority_a - $priority_b;
        }

        $priority_a = $a['priority'] ?? 10;
        $priority_b = $b['priority'] ?? 10;
        return $priority_a - $priority_b;
    }

    /**
     * Get the ID of the default page to show
     *
     * Returns the page ID that should be displayed by default when
     * no specific page is requested.
     *
     * @return string Default page ID
     * @access private
     * @since  1.0.0
     */
    private function get_default_page_id() {
        $main_config = $this->get_main_page_config();
        return $main_config['id'] ?? 'default';
    }

    /**
     * Register admin pages in WordPress
     *
     * Registers the main menu page and sets up the app shell callback.
     * Child pages are handled through the unified sidebar navigation.
     *
     * @return void
     * @access public
     * @since  1.0.0
     */
    public function register_admin_pages() {
        if ( empty( $this->pages ) ) {
            return;
        }

        // Sort pages by priority
        uasort( $this->pages, [ $this, 'sort_by_priority' ] );

        // Get the main page configuration
        $main_page_config = $this->get_main_page_config();

        add_menu_page(
            $main_page_config['page_title'] ?? 'UltraGuard Pro',
            $main_page_config['menu_title'] ?? 'UltraGuard Pro',
            $main_page_config['capability'] ?? 'manage_options',
            $main_page_config['menu_slug'] ?? $this->parent_menu_slug,
            [ $this, 'render_app_shell' ],
            $this->get_wordpress_menu_icon( (string) ( $main_page_config['icon'] ?? 'dashicons-shield' ) ),
            $main_page_config['position'] ?? null
        );

        /**
         * Action after all admin pages are registered
         *
         * @param array $pages All registered pages
         */
        do_action( 'ultraguard_admin_pages_registered', $this->pages );
    }

    /**
     * Render the logo with plan subtext
     *
     * Outputs HTML for the sidebar logo based on configuration.
     * Supports both icon and image logo types with secondary plan text.
     *
     * @return void
     * @access private
     * @since  1.0.0
     */
    private function render_logo() {
        $config = wp_parse_args( $this->logo_config, [
            'type'           => 'icon',
            'icon'           => 'fa-solid fa-shield-halved',
            'image_url'      => '',
            'primary_text'   => 'UltraGuard',
            'secondary_text' => 'Pro',
            'show_text'      => true,
        ] );

        ?>
        <div class="ultraguard-logo-container">
            <?php if ( 'image' === $config['type'] && ! empty( $config['image_url'] ) ) : ?>
                <img src="<?php echo esc_url( $config['image_url'] ); ?>"
                     alt="<?php echo esc_attr( $config['primary_text'] ); ?>"
                     class="ultraguard-app-logo-image">
            <?php else : ?>
                <?php echo wp_kses( $this->render_icon_html( (string) $config['icon'], 'ultraguard-app-logo-icon' ), $this->get_allowed_icon_html() ); ?>
            <?php endif; ?>

            <?php if ( $config['show_text'] ) : ?>
                <div class="ultraguard-app-logo-text">
                    <span class="ultraguard-app-logo-primary"><?php echo esc_html( $config['primary_text'] ); ?></span>
                    <?php if ( ! empty( $config['secondary_text'] ) ) : ?>
                        <span class="ultraguard-app-logo-secondary"><?php echo esc_html( $config['secondary_text'] ); ?></span>
                    <?php endif; ?>
                </div>
            <?php endif; ?>
        </div>
        <?php
    }

    /**
     * Render the entire App Shell (Sidebar + Content)
     *
     * Main rendering method that outputs the complete admin interface
     * including sidebar navigation and content area with free footer.
     *
     * @return void
     * @access public
     * @since  1.0.0
     */
    public function render_app_shell() {
        // Determine current page
        $current_page_id = $this->get_current_page_id();
        $page_config = $this->pages[ $current_page_id ] ?? null;

        // Check if we should show free footer on this page
        $show_free_footer = $page_config['show_free_footer'] ?? true;

        echo '<div id="ultraguard-framework-page" class="ultraguard-framework ultraguard-app-layout">';
        $this->render_app_sidebar( $current_page_id );
        echo '<main id="ultraguard-framework-content-wrapper" class="ultraguard-app-content-wrapper">';
        $this->render_page_content( $current_page_id );

        // Render free footer if not pro and page allows it
//        if ( $show_free_footer && ! $this->get_license()->isPro() ) {
//            $this->render_free_footer();
//        }
//
        echo '</main>';
        echo '</div>';
    }

    /**
     * Get current page ID from request
     *
     * Safely retrieves the current page ID from URL parameters.
     * Falls back to default page if invalid or missing.
     *
     * @return string Current page ID
     * @access private
     * @since  1.0.0
     */
    private function get_current_page_id() {
        // SECURE: Safe GET parameter access with default
        if ( isset( $_GET['ug_page'] ) && is_string( $_GET['ug_page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page routing check.
            $page_id = sanitize_key( wp_unslash( $_GET['ug_page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page routing check.
            if ( isset( $this->pages[ $page_id ] ) ) {
                return $page_id;
            }
        }
        return $this->get_default_page_id();
    }

    /**
     * Render the App Sidebar Navigation with hierarchy support
     *
     * Outputs the hierarchical sidebar navigation with groups,
     * parent-child relationships, and active state highlighting.
     *
     * @param  string $current_page_id The ID of the currently active page
     * @return void
     * @access private
     * @since  1.0.0
     */
    private function render_app_sidebar( $current_page_id ) {
        if ( ! is_string( $current_page_id ) ) {
            $current_page_id = $this->get_default_page_id();
        }

        $hierarchy = $this->build_page_hierarchy();
        $main_config = $this->get_main_page_config();
        $main_slug = $main_config['menu_slug'] ?? $this->parent_menu_slug;

        // Cache admin URL base
        static $admin_url_base = null;
        if ( null === $admin_url_base ) {
            $admin_url_base = self_admin_url( 'admin.php' );
        }

        ?>
        <aside class="ultraguard-app-sidebar">
            <div class="ultraguard-app-sidebar-header">
                <button
                    type="button"
                    class="ultraguard-app-sidebar-toggle"
                    aria-expanded="false"
                    aria-controls="ultraguard-app-sidebar-nav"
                    aria-label="<?php echo esc_attr__( 'Toggle navigation', 'ultraguard-security-suite' ); ?>"
                >
                    <span class="dashicons dashicons-menu-alt3" aria-hidden="true"></span>
                    <span class="screen-reader-text"><?php esc_html_e( 'Toggle navigation', 'ultraguard-security-suite' ); ?></span>
                </button>
                <div class="ultraguard-app-logo">
                    <?php $this->render_logo(); ?>
                </div>
            </div>
            <div class="ultraguard-app-sidebar-drawer" role="navigation" aria-label="Main navigation">
                <nav id="ultraguard-app-sidebar-nav" class="ultraguard-app-sidebar-nav">
                    <?php
                    // Group top-level pages by group
                    $grouped_pages = [];
                    foreach ( $hierarchy as $page_id => $page_data ) {
                        $page = $page_data['page'] ?? null;
                        if ( ! $page ||
                             ( ! empty( $page['parent_id'] ) ) ||
                             ( ! empty( $page['is_hidden'] ) ) ) {
                            continue;
                        }
                        $group = $page['group'] ?? __( 'General', 'ultraguard-security-suite' );
                        if ( ! isset( $grouped_pages[ $group ] ) ) {
                            $grouped_pages[ $group ] = [];
                        }
                        $grouped_pages[ $group ][ $page_id ] = $page_data;
                    }

                    foreach ( $grouped_pages as $group_name => $pages_data ) :
                    ?>
                        <div class="ultraguard-app-sidebar-group">
                            <?php if ( __( 'General', 'ultraguard-security-suite' ) !== $group_name ) : ?>
                                <h4 class="ultraguard-app-sidebar-group-title"><?php echo esc_html( $group_name ); ?></h4>
                            <?php endif; ?>

                            <?php
                            foreach ( $pages_data as $page_id => $page_data ) :
                                $page = $page_data['page'] ?? [];
                                $children = $page_data['children'] ?? [];

                                // Check capabilities
                                $capability = $page['capability'] ?? 'manage_options';
                                $can_access_page = 'manage_options' === $capability
                                    ? \UltraguardPro\ultraguard_current_user_can_manage()
                                    : current_user_can( $capability );

                                if ( ! $can_access_page ) {
                                    continue;
                                }

                                $is_active = ( $page_id === $current_page_id );
                                $has_children = ! empty( $children );
                                $is_expanded = $is_active || $this->is_child_active( $page_id, $current_page_id );

                                // For parent items with children, don't create a direct link
                                if ( $has_children ) {
                                    $link = '#';
                                } else {
                                    $link = add_query_arg(
                                        [
                                            'page'    => $main_slug,
                                            'ug_page' => $page_id,
                                        ],
                                        $admin_url_base
                                    );
                                }

                                $icon_class = $page['icon'] ?? 'dashicons-admin-generic';
                                $sidebar_item_class = trim( 'ultraguard-app-sidebar-item ' . ( $has_children ? 'has-children' : '' ) );
                                $sidebar_link_class = trim( 'ultraguard-app-sidebar-link ' . ( $is_active ? 'active' : '' ) );
                                ?>

                                <div class="<?php echo esc_attr( $sidebar_item_class ); ?>">
                                    <a href="<?php echo $has_children ? '#' : esc_url( $link ); ?>"
                                      class="<?php echo esc_attr( $sidebar_link_class ); ?>"
                                      <?php if ( $has_children ) : ?>
                                          role="button"
                                          aria-expanded="<?php echo esc_attr( $is_expanded ? 'true' : 'false' ); ?>"
                                          data-page-url="<?php echo esc_url( $link ); ?>"
                                      <?php endif; ?>>
                                       <?php echo wp_kses( $this->render_icon_html( (string) $icon_class, 'ultraguard-app-sidebar-link-icon' ), $this->get_allowed_icon_html() ); ?>
                                       <span class="ultraguard-app-sidebar-link-text">
                                           <?php echo esc_html( $page['menu_title'] ?? $page_id ); ?>
                                       </span>
                                       <?php if ( $has_children ) : ?>
                                           <span class="ultraguard-app-sidebar-arrow dashicons dashicons-arrow-<?php echo esc_attr( $is_expanded ? 'down' : 'right' ); ?>"></span>
                                       <?php endif; ?>
                                   </a>

                                   <?php if ( $has_children ) : ?>
                                       <div class="<?php echo esc_attr( trim( 'ultraguard-app-sidebar-children ' . ( $is_expanded ? 'expanded' : '' ) ) ); ?>"
                                            aria-hidden="<?php echo esc_attr( $is_expanded ? 'false' : 'true' ); ?>">
                                           <?php
                                           // Sort children to ensure overview is first
                                           $sorted_children = $children;
                                           uasort( $sorted_children, [ $this, 'sort_child_pages' ] );

                                           foreach ( $sorted_children as $child_id => $child_page ) :
                                               // Check capabilities and visibility
                                               $child_capability = $child_page['capability'] ?? 'manage_options';
                                               $child_hidden = ! empty( $child_page['is_hidden'] );
                                               $is_parent_overview = ! empty( $child_page['is_parent_overview'] );

                                               $can_access_child = 'manage_options' === $child_capability
                                                   ? \UltraguardPro\ultraguard_current_user_can_manage()
                                                   : current_user_can( $child_capability );

                                               if ( ! $can_access_child || $child_hidden ) {
                                                   continue;
                                               }

                                               // For overview pages, use the original page ID
                                               $actual_child_id = $is_parent_overview ? $page_id : $child_id;
                                               $is_child_active = ( $actual_child_id === $current_page_id );

                                               $child_link = add_query_arg(
                                                   [
                                                       'page'    => $main_slug,
                                                       'ug_page' => $actual_child_id,
                                                   ],
                                                   $admin_url_base
                                               );

                                               $child_icon_class = $child_page['icon'] ?? 'dashicons-admin-generic';
                                               $child_menu_title = $child_page['menu_title'] ?? $child_id;
                                               $child_link_class = trim(
                                                   'ultraguard-app-sidebar-child-link '
                                                   . ( $is_child_active ? 'active ' : '' )
                                                   . ( $is_parent_overview ? 'ultraguard-parent-overview' : '' )
                                               );
                                               ?>
                                               <a href="<?php echo esc_url( $child_link ); ?>"
                                                  class="<?php echo esc_attr( trim( $child_link_class ) ); ?>"
                                                  <?php if ( $is_parent_overview ) : ?>
                                                      data-is-parent-overview="true"
                                                  <?php endif; ?>
                                                  role="menuitem">
                                                   <?php echo wp_kses( $this->render_icon_html( (string) $child_icon_class, 'ultraguard-app-sidebar-child-icon' ), $this->get_allowed_icon_html() ); ?>
                                                   <span class="ultraguard-app-sidebar-link-text">
                                                       <?php echo esc_html( $child_menu_title ); ?>
                                                   </span>
                                               </a>
                                           <?php endforeach; ?>
                                       </div>
                                   <?php endif; ?>
                               </div>
                           <?php endforeach; ?>
                       </div>
                   <?php endforeach; ?>
               </nav>

               <?php $this->render_sidebar_upgrade_cta(); ?>
           </div>
       </aside>
       <?php
    }

    /**
     * Render sidebar upgrade CTA for free users
     *
     * @return void
     * @access private
     * @since  1.0.0
     */
    private function render_sidebar_upgrade_cta() {
        if ( $this->get_license()->isPro() ) {
            return;
        }

        $delay_days      = LicenseInfo::FREE_UPDATE_DELAY;
        $compare_url     = self_admin_url( \UltraguardPro\support\UpgradeManager::UPGRADE_PAGE_URL_PATTERN );
$external_url    = \UltraguardPro\support\UpgradeManager::external_upgrade_url();
        ?>
        <div class="ultraguard-sidebar-upgrade-cta">
            <div class="ultraguard-sidebar-upgrade-content">
                <span class="dashicons dashicons-awards ug-cta-icon" aria-hidden="true"></span>
                <h4><?php esc_html_e( 'UltraGuard Pro', 'ultraguard-security-suite' ); ?></h4>
                <p>
                    <?php
                    echo esc_html(
                        sprintf(
                            /* translators: %d: number of days intelligence updates are delayed on the free plan. */
                            esc_html__( 'Threat updates delayed %d days on Free. Pro gets them instantly.', 'ultraguard-security-suite' ),
                            $delay_days
                        )
                    );
                    ?>
                </p>
                <div class="ug-cta-buttons">
                    <a href="<?php echo esc_url( $compare_url ); ?>" class="ug-cta-btn ug-cta-btn-primary">
                        <?php esc_html_e( 'See What\'s in Pro', 'ultraguard-security-suite' ); ?>
                    </a>
                    <a href="<?php echo esc_url( $external_url ); ?>" target="_blank" rel="noopener noreferrer" class="ug-cta-btn ug-cta-btn-link">
                        <?php esc_html_e( 'View pricing ↗', 'ultraguard-security-suite' ); ?>
                    </a>
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Render free footer similar to MailManager
     *
     * @return void
     * @access private
     * @since  1.0.0
     */
    private function render_free_footer() {
        $site_name = esc_html( get_bloginfo( 'name' ) );
        $site_url = esc_url( get_bloginfo( 'url' ) );
        $year = gmdate( 'Y' );
        $version = defined( 'ULTRAGUARD_PRO_VERSION' ) ? esc_html( ULTRAGUARD_PRO_VERSION ) : '';
        $compare_url = esc_url( self_admin_url( \UltraguardPro\support\UpgradeManager::UPGRADE_PAGE_URL_PATTERN ) );
        $upgrade_url = esc_url( \UltraguardPro\support\UpgradeManager::external_upgrade_url() );
        $delay_days = LicenseInfo::FREE_UPDATE_DELAY;

        $dismissed = get_transient( self::FOOTER_DISMISS_TRANSIENT . '_' . get_current_user_id() );
        $footer_class = $dismissed ? 'ultraguard-footer-dismissed' : '';
        ?>
        <div class="ultraguard-free-footer <?php echo esc_attr( $footer_class ); ?>" id="ultraguard-free-footer">
            <div class="ultraguard-footer-content">
                <div class="ultraguard-footer-warning">
                    <span class="dashicons dashicons-warning"></span>
                    <div class="ultraguard-footer-message">
                        <strong><?php esc_html_e( 'Free Plan — Definition updates delivered within 24 hours.', 'ultraguard-security-suite' ); ?></strong>
                        <p>
                            <?php
                            echo esc_html(
                                sprintf(
                                    /* translators: %d: number of days malware, antivirus, and firewall updates are delayed on the free plan. */
                                    esc_html__(
                                    'Your malware signatures, antivirus database and firewall rules are pushed %d days after release. During that window your site is not protected against newly discovered threats.',
                                    'ultraguard-security-suite'
                                    ),
                                    $delay_days
                                )
                            );
                            ?>
                            <a href="<?php echo esc_url( $upgrade_url ); ?>" target="_blank" class="ultraguard-upgrade-link">
                                <?php esc_html_e( 'Upgrade to UltraGuard Pro', 'ultraguard-security-suite' ); ?>
                            </a>
                            <?php esc_html_e( 'for real-time updates and priority threat response.', 'ultraguard-security-suite' ); ?>
                        </p>
                    </div>
                </div>

                <div class="ultraguard-footer-actions">
                    <a href="<?php echo esc_url( $upgrade_url ); ?>" target="_blank" class="button button-primary">
                        <span class="dashicons dashicons-arrow-up-alt"></span>
                        <?php esc_html_e( 'Upgrade Now', 'ultraguard-security-suite' ); ?>
                    </a>
                    <button type="button" class="button button-link ultraguard-dismiss-footer" id="ultraguard-dismiss-footer">
                        <span class="dashicons dashicons-no-alt"></span>
                        <span class="screen-reader-text"><?php esc_html_e( 'Dismiss for 7 days', 'ultraguard-security-suite' ); ?></span>
                    </button>
                </div>
            </div>

            <div class="ultraguard-footer-branding">
                <strong><?php esc_html_e( 'UltraGuard', 'ultraguard-security-suite' ); ?></strong>
                <?php
                echo esc_html(
                    sprintf(
                        /* translators: 1: UltraGuard version string. 2: copyright year. */
                        esc_html__( ' %1$s © %2$s All rights reserved.', 'ultraguard-security-suite' ),
                        $version,
                        $year
                    )
                );
                ?>
            </div>
        </div>

        <?php $this->render_footer_styles(); ?>
        <?php $this->render_footer_script(); ?>
        <?php
    }

    /**
     * Render footer styles
     *
     * @return void
     * @access private
     */
    private function render_footer_styles() {
        ?>
        <style>

        </style>
        <?php
    }

    /**
     * Render footer script for dismiss functionality
     *
     * @return void
     * @access private
     */
    private function render_footer_script() {
        ?>
        <script type="text/javascript">
            jQuery(document).ready(function($) {
                $('#ultraguard-dismiss-footer').on('click', function() {
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'ultraguard_dismiss_free_footer',
                            nonce: <?php echo wp_json_encode( wp_create_nonce( 'ultraguard_dismiss_footer' ) ); ?>
                        },
                        success: function() {
                            $('#ultraguard-free-footer').addClass('ultraguard-footer-dismissed');
                        }
                    });
                });
            });
        </script>
        <?php
    }

    /**
     * Check if any child of a page is active
     *
     * Determines whether any child page of a given parent is currently active.
     *
     * @param  string $parent_id       Parent page ID
     * @param  string $current_page_id Current active page ID
     * @return bool                   True if any child is active, false otherwise
     * @access private
     * @since  1.0.0
     */
    private function is_child_active( $parent_id, $current_page_id ) {
        if ( ! is_string( $parent_id ) || ! is_string( $current_page_id ) ) {
            return false;
        }

        $children = $this->get_child_pages( $parent_id );
        return isset( $children[ $current_page_id ] );
    }

    /**
     * Render admin page content
     *
     * Renders the content for a specific admin page using the render engine.
     * Handles capability checks, context building, and filtering.
     *
     * @param  string $page_id The ID of the page to render
     * @return void
     * @access public
     * @since  1.0.0
     */
    public function render_page_content( $page_id ) {
        if ( ! is_string( $page_id ) ) {
            $page_id = $this->get_default_page_id();
        }

        // Find page configuration by ID
        if ( ! isset( $this->pages[ $page_id ] ) ) {
            $page_id = $this->get_default_page_id();
        }

        $page_config = $this->pages[ $page_id ] ?? null;

        if ( ! $page_config ) {
            wp_die( esc_html__( 'Page configuration not found', 'ultraguard-security-suite' ) );
        }

        // Check user capability
        $capability = $page_config['capability'] ?? 'manage_options';
        $can_access_page = 'manage_options' === $capability
            ? \UltraguardPro\ultraguard_current_user_can_manage()
            : current_user_can( $capability );

        if ( ! $can_access_page ) {
            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'ultraguard-security-suite' ) );
        }

        // Build context with license info
        $context = [
            'current_page' => $page_config['menu_slug'] ?? $this->parent_menu_slug,
            'page_id'      => $page_id,
            'page_config'  => $page_config,
            'nonce'        => wp_create_nonce( 'ultraguard_admin_nonce' ),
            'license'      => [
                'is_pro'   => $this->get_license()->isPro(),
                'is_valid' => $this->get_license()->isValid(),
                'status'   => $this->get_license()->getStatus(),
                'delay'    => $this->get_license()->getUpdateDelay(),
            ],
        ];

        /**
         * Filter page context before rendering
         *
         * @param array  $context     Page context data
         * @param string $page_id     Page identifier
         * @param array  $page_config Page configuration
         */
        $context = apply_filters( 'ultraguard_page_context', $context, $page_id, $page_config );
        $context = apply_filters( "ultraguard_page_context_{$page_id}", $context, $page_config );

        // Get scheme from the registered callback.
        // Supports two formats:
        //   1. [object, 'method']          — legacy eager format (object already newed)
        //   2. [ClassName::class, $plugin] — lazy format: class is instantiated HERE,
        //                                    only when this page is actually being rendered.
        $scheme_callback = $page_config['scheme_callback'] ?? null;

        // ── Resolve lazy callback ──────────────────────────────────────────
        // Format: ['scheme_callback' => [MyPage::class, $plugin]]
        //   $scheme_callback[0] is a string class name
        //   $scheme_callback[1] is the Plugin instance (constructor arg)
        if ( is_array( $scheme_callback )
             && isset( $scheme_callback[0], $scheme_callback[1] )
             && is_string( $scheme_callback[0] )
             && class_exists( $scheme_callback[0] )
        ) {
            $class_name      = $scheme_callback[0];
            $constructor_arg = $scheme_callback[1];
            $scheme_callback = [ new $class_name( $constructor_arg ), 'get_scheme' ];
        }

        if ( ! is_callable( $scheme_callback ) ) {
            wp_die( esc_html__( 'Invalid scheme callback', 'ultraguard-security-suite' ) );
        }

        $scheme = call_user_func( $scheme_callback, $context );

        // Validate scheme
        if ( ! is_array( $scheme ) ) {
            wp_die( esc_html__( 'Invalid page scheme returned from callback', 'ultraguard-security-suite' ) );
        }

        /**
         * Filter page scheme before rendering
         *
         * @param array  $scheme  Page scheme configuration
         * @param string $page_id Page identifier
         * @param array  $context Page context
         */
        $scheme = apply_filters( 'ultraguard_page_scheme', $scheme, $page_id, $context );
        $scheme = apply_filters( "ultraguard_page_scheme_{$page_id}", $scheme, $context );

        /**
         * Action before page render
         *
         * @param string $page_id Page identifier
         * @param array  $scheme  Page scheme configuration
         * @param array  $context Page context
         */
        do_action( 'ultraguard_before_page_render', $page_id, $scheme, $context );

        // Render the page
        $output = $this->render_engine->render( $scheme, $context );

        /**
         * Filter rendered page output
         *
         * @param string $output  Rendered HTML output
         * @param string $page_id Page identifier
         * @param array  $scheme  Page scheme configuration
         */
        $output = apply_filters( 'ultraguard_page_output', $output, $page_id, $scheme );
        $output = apply_filters( "ultraguard_page_output_{$page_id}", $output, $scheme );

        // $output is plugin-generated HTML assembled from scheme arrays.
        // We use a custom allowed-HTML map (not wp_kses_post) because form elements
        // and action buttons rely on data-* attributes (data-ajax, data-ajax-action,
        // data-nonce, data-confirm, etc.) that wp_kses_post strips entirely.
        echo wp_kses( $output, self::get_plugin_allowed_html() );

        echo wp_kses( RenderEngine::render_footer_modals(), self::get_plugin_allowed_html() );

        /**
         * Action after page render
         *
         * @param string $page_id Page identifier
         * @param array  $scheme  Page scheme configuration
         * @param array  $context Page context
         */
        do_action( 'ultraguard_after_page_render', $page_id, $scheme, $context );
    }

    /**
     * Show free footer notice (if not dismissed)
     *
     * @return void
     * @access public
     * @since  1.0.0
     */
    /**
     * Add body class to UltraGuard admin pages.
     * Allows JS to detect UltraGuard context and start realtime polling.
     *
     * @param  string $classes Existing body classes
     * @return string          Modified body classes
     */
    public function add_admin_body_class( string $classes ): string {
        $screen = get_current_screen();
        if ( $screen && strpos( $screen->id, 'ultraguard' ) !== false ) {
            $classes .= ' ultraguard-admin-page';
        }
        return $classes;
    }

    public function maybe_show_free_footer_notice() {
        // Only show on Ultraguard pages
        $screen = get_current_screen();
        if ( ! $screen || strpos( $screen->id, 'ultraguard' ) === false ) {
            return;
        }

        // Don't show if pro
        if ( $this->get_license()->isPro() ) {
            return;
        }

        // Check if dismissed
        $dismissed = get_transient( self::FOOTER_DISMISS_TRANSIENT . '_' . get_current_user_id() );
        if ( $dismissed ) {
            return;
        }

        $delay_days = LicenseInfo::FREE_UPDATE_DELAY;
        $compare_url = self_admin_url( \UltraguardPro\support\UpgradeManager::UPGRADE_PAGE_URL_PATTERN );
$upgrade_url = \UltraguardPro\support\UpgradeManager::external_upgrade_url();
        ?>
        <div class="notice notice-warning is-dismissible ultraguard-admin-notice">
            <p>
                <strong><?php esc_html_e( 'UltraGuard Free Plan:', 'ultraguard-security-suite' ); ?></strong>
                <?php
                echo esc_html(
                    sprintf(
                        /* translators: %d: number of days security definition updates are delayed. */
                        esc_html__(
                            'Security definition updates are delayed by %d days.',
                            'ultraguard-security-suite'
                        ),
                        $delay_days
                    )
                );
                ?>
                <a href="<?php echo esc_url( $upgrade_url ); ?>" target="_blank" style="font-weight:600;color:#b45309;">
                    <?php esc_html_e( 'Upgrade to Pro', 'ultraguard-security-suite' ); ?>
                </a>
                <?php esc_html_e( 'for real-time protection.', 'ultraguard-security-suite' ); ?>
            </p>
            <button type="button" class="notice-dismiss ultraguard-notice-dismiss" data-notice="free-footer">
                <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'ultraguard-security-suite' ); ?></span>
            </button>
        </div>

        <script>
        jQuery(document).ready(function($) {
            $('.ultraguard-notice-dismiss').on('click', function() {
                $.post(ajaxurl, {
                    action: 'ultraguard_dismiss_free_footer',
                    nonce: <?php echo wp_json_encode( wp_create_nonce( 'ultraguard_dismiss_footer' ) ); ?>
                });
                $(this).closest('.notice').fadeOut();
            });
        });
        </script>
        <?php
    }

    /**
     * AJAX handler for dismissing free footer
     *
     * @return void
     * @access public
     * @since  1.0.0
     */
    public function ajax_dismiss_free_footer() {
        // Verify nonce
        if ( ! check_ajax_referer( 'ultraguard_dismiss_footer', 'nonce', false ) ) {
            wp_die( esc_html__( 'Invalid nonce.', 'ultraguard-security-suite' ) );
        }

        // Set transient for current user (7 days)
        set_transient(
            self::FOOTER_DISMISS_TRANSIENT . '_' . get_current_user_id(),
            true,
            self::FOOTER_DISMISS_DURATION
        );

        wp_die( '1' );
    }

    /**
     * Conditionally enqueue assets
     *
     * Enqueues CSS and JavaScript assets only on Ultraguard Pro admin pages.
     * Uses caching for performance and allows filtering of localized data.
     *
     * @param  string $hook Current admin page hook suffix
     * @return void
     * @access public
     * @since  1.0.0
     */
    /**
     * Register shared script handles on ALL admin pages (priority 1).
     *
     * Modules like FilecheckerModule enqueue their own scripts on every admin
     * page and declare `ultraguard-managers` / `ultraguard-realtime` as deps.
     * Those handles must be *registered* (not necessarily enqueued) before any
     * module script is enqueued — otherwise WordPress 6.9+ throws a notice and
     * the dependency chain silently breaks.
     *
     * This method registers without enqueuing.  `maybe_enqueue_assets()` then
     * calls `wp_enqueue_script()` on the same handles for the plugin's own pages,
     * which is safe to call on an already-registered handle.
     *
     * @return void
     */
    public function register_shared_scripts(): void {
        $plugin_url     = defined( 'ULTRAGUARD_PRO_URL' )     ? ULTRAGUARD_PRO_URL     : '';
        $plugin_version = defined( 'ULTRAGUARD_PRO_VERSION' ) ? ULTRAGUARD_PRO_VERSION : '1.0.0';

        if ( empty( $plugin_url ) ) {
            return;
        }

        // chart-js — needed by ultraguard-core
        wp_register_script(
            'chart-js',
            $plugin_url . 'assets/js/chart.min.js',
            [ 'jquery' ],
            '4.5.0',
            true
        );

        // Core namespace bootstrap — must be first
        wp_register_script(
            'ultraguard-core',
            $plugin_url . 'assets/js/ultraguard-core.js',
            [ 'jquery', 'chart-js', 'wp-util' ],
            $plugin_version,
            true
        );

        // Managers — TabManager, ScanManager, PollingManager, etc.
        wp_register_script(
            'ultraguard-managers',
            $plugin_url . 'assets/js/ultraguard-managers.js',
            [ 'ultraguard-core' ],
            $plugin_version,
            true
        );

        // Components — shared UI widgets
        wp_register_script(
            'ultraguard-components',
            $plugin_url . 'assets/js/ultraguard-components.js',
            [ 'ultraguard-managers' ],
            $plugin_version,
            true
        );

        // Modals — dialog layer
        wp_register_script(
            'ultraguard-modals',
            $plugin_url . 'assets/js/ultraguard-modals.js',
            [ 'ultraguard-components' ],
            $plugin_version,
            true
        );

        // Real-time SSE / polling engine — depends on ultraguard-managers for
        // UltraGuardManagers.RealtimeManager and showToast helpers
        wp_register_script(
            'ultraguard-realtime',
            $plugin_url . 'assets/js/ultraguard-realtime.js',
            [ 'jquery', 'ultraguard-managers' ],
            $plugin_version,
            true
        );
    }

    public function maybe_enqueue_assets( $hook ) {
        // Early return if no pages registered
        if ( empty( $this->pages ) ) {
            return;
        }

        // Cache the hook check
        if ( null === $this->current_admin_hook ) {
            $main_config = $this->get_main_page_config();
            $main_slug = $main_config['menu_slug'] ?? $this->parent_menu_slug;
            $this->current_admin_hook = 'toplevel_page_' . $main_slug;
        }

        // Check if we're on our admin page
        if ( $hook !== $this->current_admin_hook ) {
            return;
        }

        /**
         * Action before assets enqueue
         *
         * @param string $hook Current admin page hook
         */
        do_action( 'ultraguard_before_enqueue_assets', $hook );

        // Get plugin constants with defaults
        $plugin_url = defined( 'ULTRAGUARD_PRO_URL' ) ? ULTRAGUARD_PRO_URL : '';
        $plugin_version = defined( 'ULTRAGUARD_PRO_VERSION' ) ? ULTRAGUARD_PRO_VERSION : '1.0.0';

        if ( empty( $plugin_url ) ) {
            return;
        }

        wp_enqueue_style(
            'ultraguard-fontawesome',
            $plugin_url . 'assets/css/fontawesome-all.min.css',
            [],
            '6.5.2'
        );

        // Enqueue Main CSS
        wp_enqueue_style(
            'admin-base',
            $plugin_url . 'assets/css/admin-base.css',
            [ 'ultraguard-fontawesome' ],
            $plugin_version
        );

        wp_enqueue_style(
            'admin-components',
            $plugin_url . 'assets/css/admin-components.css',
            ['admin-base'],
            $plugin_version
        );

        wp_enqueue_style(
            'admin-security',
            $plugin_url . 'assets/css/admin-security.css',
            ['admin-components'],
            $plugin_version
        );

        wp_enqueue_style(
            'admin-utilities',
            $plugin_url . 'assets/css/admin-utilities.css',
            ['admin-security'],
            $plugin_version
        );

        // Real-time widget styles
        wp_enqueue_style(
            'ultraguard-realtime',
            $plugin_url . 'assets/css/admin-realtime.css',
            ['admin-utilities'],
            $plugin_version
        );

        // Extranet theme — overrides colour tokens and layout to MicroOffice style
        wp_enqueue_style(
            'ultraguard-theme',
            $plugin_url . 'assets/css/admin-theme.css',
            ['ultraguard-realtime'],
            $plugin_version
        );

        // Enqueue JavaScript
        // Scripts already registered at priority 1 by register_shared_scripts().
        // Just enqueue by handle — WordPress will use the registered URL/deps/version.
        wp_enqueue_script( 'chart-js' );

        // core.js must load first — it establishes window.UltraGuardPro namespace
        // that managers.js, components.js, and modals.js depend on at parse time.
        wp_enqueue_script( 'ultraguard-core' );

        wp_enqueue_script( 'ultraguard-managers' );

        wp_enqueue_script( 'ultraguard-components' );

        wp_enqueue_script( 'ultraguard-modals' );

        // Real-time SSE / polling engine — depends on ultraguard-managers
        wp_enqueue_script( 'ultraguard-realtime' );

        // Localize script data with license info
        $localize_data = [
            'ajaxurl'         => self_admin_url( 'admin-ajax.php' ),
            'nonce'           => wp_create_nonce( 'ultraguard_admin_nonce' ),
            'realtime_nonce'  => wp_create_nonce( 'ultraguard_admin_nonce' ),
            'dismiss_nonce'   => wp_create_nonce( 'ultraguard_dismiss_footer' ),
            'debug'           => defined( 'WP_DEBUG' ) && WP_DEBUG,
            'license'         => [
                'is_pro'   => $this->get_license()->isPro(),
                'is_valid' => $this->get_license()->isValid(),
                'delay'    => $this->get_license()->getUpdateDelay(),
                'status'   => $this->get_license()->getStatus(),
            ],
            'i18n'    => [
                'loading'  => __( 'Loading...', 'ultraguard-security-suite' ),
                'error'    => __( 'An error occurred', 'ultraguard-security-suite' ),
                'success'  => __( 'Success!', 'ultraguard-security-suite' ),
                'upgrade'  => __( 'Upgrade to Pro', 'ultraguard-security-suite' ),
                'dismiss'  => __( 'Dismiss for 7 days', 'ultraguard-security-suite' ),
                'scanning' => __( 'Scanning…', 'ultraguard-security-suite' ),
                'run_scan' => __( 'Run Scan', 'ultraguard-security-suite' ),
                'never'    => __( 'Never', 'ultraguard-security-suite' ),
            ],
        ];

        /**
         * Filter localized script data
         *
         * @param array  $localize_data Data to pass to JavaScript
         * @param string $hook          Current admin page hook
         */
        // Map admin page → SSE channels to subscribe on that page
        // Only subscribed channels are streamed — zero idle polling on unrelated pages
        // Keys match $_GET['ug_page'] values (short page IDs, not menu slugs)
        $page_channel_map = [
            // Dashboard (also matched when ug_page is absent and page=ultraguard-security-suite-dashboard)
            'dashboard'              => [ 'security_score', 'threat_feed', 'firewall_status' ],
            'ultraguard-security-suite'         => [ 'security_score', 'threat_feed', 'firewall_status' ],
            // Antivirus
            'antivirus'              => [ 'antivirus_status', 'threat_feed' ],
            'file-scanner'           => [ 'antivirus_status' ],
            'malware-detection'      => [ 'antivirus_status' ],
            'antivirus-scanner'      => [ 'antivirus_status' ],  // correct slug (no collision with vuln)
            'scan-history'           => [ 'antivirus_status' ],
            'quarantine'             => [ 'antivirus_status' ],
            'malware-signature'      => [ 'antivirus_status' ],
            'scanner-settings'       => [ 'antivirus_status' ],
            // File checker / integrity
            'filechecker'            => [ 'file_scanner_status', 'filechecker_progress' ],
            'filechecker-changes'    => [ 'file_scanner_status', 'filechecker_progress' ],
            'filechecker-history'    => [ 'file_scanner_status' ],
            'filechecker-monitoring' => [ 'file_scanner_status' ],
            'filechecker-quarantine' => [ 'file_scanner_status' ],
            'filechecker-settings'   => [ 'file_scanner_status' ],
            // Firewall
            'firewall'               => [ 'firewall_status' ],
            'firewall-logs'          => [ 'firewall_status' ],
            'firewall-rules'         => [ 'firewall_status' ],
            'firewall-add-rules'     => [ 'firewall_status' ],
            'firewall-country-blocking' => [ 'firewall_status' ],
            'firewall-settings'      => [ 'firewall_status' ],
            // Login limiter
            'login_limiter'          => [ 'login_status' ],
            'ip-management'          => [ 'login_status', 'firewall_status' ],
            'lockouts'               => [ 'login_status' ],
            'login-attempts'         => [ 'login_status' ],
            'login-limiter-settings' => [ 'login_status' ],
            // Htaccess
            'htaccess'               => [ 'htaccess_status' ],
            'htaccess-security-events' => [ 'htaccess_status', 'firewall_status' ],
            'htaccess-security-rules'  => [ 'htaccess_status' ],
            'htaccess-custom-rules'    => [ 'htaccess_status' ],
            'htaccess-backups'         => [ 'htaccess_status' ],
            'htaccess-settings'        => [ 'htaccess_status' ],
            // Dashboard settings
            'dashboard-settings'     => [ 'security_score' ],
            // Database Scanner
            'database-scanner'       => [ 'db_scan_status' ],
            // Vulnerability Scanner
            'vulnerability-scanner'  => [ 'vuln_scan_status' ],
            'vulnerability-results'  => [ 'vuln_scan_status' ],
            'virtual-patches'        => [ 'vuln_scan_status' ],
        ];

        // Derive current page ID from ug_page param (the real sub-page ID)
        // $_GET['page'] always equals the parent menu slug, not the sub-page.
        // $_GET['ug_page'] holds the actual page ID like 'antivirus', 'firewall', etc.
        $current_page_slug = sanitize_key( isset( $_GET['ug_page'] ) ? wp_unslash( $_GET['ug_page'] ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only current page detection.
        // Fallback: if no ug_page, use 'page' param for the root dashboard
        if ( empty( $current_page_slug ) ) {
            $current_page_slug = sanitize_key( isset( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only current page detection.
            // Strip the 'ultraguard-security-suite-' prefix to match our map keys
            $current_page_slug = preg_replace( '/^ultraguard-security-suite-/', '', $current_page_slug );
        }
        $rt_channels = $page_channel_map[ $current_page_slug ] ?? [];

        // Allow any registered filter to add/remove channels for this page
        $rt_channels = apply_filters( 'ultraguard_rt_channels', $rt_channels, $current_page_slug, $hook );
        $localize_data['rt_channels'] = array_values( array_unique( $rt_channels ) );

        $localize_data = apply_filters( 'ultraguard_localize_script_data', $localize_data, $hook );

        wp_localize_script( 'ultraguard-core', 'ultraguardAdmin', $localize_data );

        // Alias as 'ultraguardPro' so JS files that reference ultraguardPro.ajaxurl work correctly.
        // Both objects carry identical data; JS should prefer ultraguardPro going forward.
        wp_localize_script( 'ultraguard-core', 'ultraguardPro', $localize_data );

        // Pass same config to real-time engine (ultraguardAdmin already set on window)
        wp_localize_script( 'ultraguard-realtime', 'ultraguardAdmin', $localize_data );

        // Enqueue code editor
        if ( function_exists( 'wp_enqueue_code_editor' ) ) {
            wp_enqueue_code_editor( [ 'type' => 'text/html' ] );
        }

        /**
         * Action after assets enqueue
         *
         * @param string $hook Current admin page hook
         */
        do_action( 'ultraguard_after_enqueue_assets', $hook );
    }

    /**
     * Get registered pages
     *
     * Returns all registered page configurations.
     *
     * @return array Registered pages with page IDs as keys
     * @access public
     * @since  1.0.0
     */
    public function get_pages() {
        return $this->pages;
    }

    /**
     * Get render engine instance
     *
     * Returns the render engine instance, initializing it if necessary.
     *
     * @return \UltraguardPro\view\RenderEngine|null Render engine instance or null
     * @access public
     * @since  1.0.0
     */
    public function get_render_engine() {
        $this->init_components();
        return $this->render_engine;
    }

    /**
     * Get parent menu slug
     *
     * Returns the WordPress menu slug for the main parent menu item.
     *
     * @return string Parent menu slug
     * @access public
     * @since  1.0.0
     */
    public function get_parent_menu_slug() {
        // Ensure it's set correctly
        $this->get_main_page_config();
        return $this->parent_menu_slug;
    }

    /**
     * Get logo configuration
     *
     * Returns the current logo configuration array.
     *
     * @return array Logo configuration
     * @access public
     * @since  1.0.0
     */
    public function get_logo_config() {
        return $this->logo_config;
    }

    /**
     * Update logo configuration
     *
     * Updates the logo configuration with new values, merging with existing.
     *
     * @param  array $config New logo configuration values
     * @return void
     * @access public
     * @since  1.0.0
     */
    public function set_logo_config( $config ) {
        // SECURE: Ensure config is always an array
        $config = is_array( $config ) ? $config : [];
        $this->logo_config = wp_parse_args( $config, $this->logo_config );
    }


    /**
     * Check if showing free footer
     *
     * @return bool
     * @access public
     * @since  1.0.0
     */
    public function is_showing_free_footer() {
        return ! $this->get_license()->isPro() && $this->show_free_footer;
    }

    /**
     * Enable/disable free footer
     *
     * @param bool $show
     * @return void
     * @access public
     * @since  1.0.0
     */
    public function set_show_free_footer( $show ) {
        $this->show_free_footer = (bool) $show;
    }

    /**
    * Get license instance (lazy loaded)
    *
    * @return \UltraguardPro\support\LicenseInfo
    */
   private function get_license() {
       if (null === $this->license) {
           $this->license = LicenseInfo::make();
       }
       return $this->license;
   }

    // =========================================================================
    // STATIC GUARD HELPERS — used by DashboardContributors
    // =========================================================================

    /**
     * Check whether the current request is an UltraGuard admin page.
     *
     * Safe to call from any hook (admin_init, init, etc.).
     * Returns false on the frontend, REST API, cron, and all non-UG admin pages.
     *
     * @return bool
     */
    public static function is_ultraguard_page(): bool {
        if ( ! is_admin() ) { return false; }
        // wp_doing_ajax() / REST / cron checks
        if ( wp_doing_ajax() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || wp_doing_cron() ) {
            return false;
        }
        // The parent menu slug is always 'ultraguard-security-suite-dashboard' (set by register_admin_pages)
        // $_GET['page'] matches toplevel_page_ hook
        $page = sanitize_key( wp_unslash( $_GET['page'] ?? ''  ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only plugin screen detection.
        return str_starts_with( $page, 'ultraguard-security-suite' );
    }

    /**
     * Check whether the current request is the UltraGuard Dashboard page specifically.
     * Only the 'dashboard' ug_page (or root with no ug_page) qualifies.
     *
     * Contributors should use this to gate DB-query work so it only runs when
     * the dashboard is actually being rendered.
     *
     * @return bool
     */
    public static function is_ultraguard_dashboard(): bool {
        if ( ! self::is_ultraguard_page() ) { return false; }
        $ug_page = sanitize_key( wp_unslash( $_GET['ug_page'] ?? 'dashboard'  ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only plugin screen detection.
        return $ug_page === 'dashboard' || $ug_page === '';
    }

    /**
     * Build the allowed-HTML map used when echoing rendered page output.
     *
     * wp_kses_post() is intentionally NOT used here because it does not permit
     * data-* attributes, which are required by every AJAX button and form
     * element in the plugin (data-ajax-action, data-nonce, data-confirm, etc.).
     *
     * This map is deliberately wide-open for data-* so that any attribute
     * emitted by the render layer survives sanitisation. Non-data attributes
     * are enumerated explicitly so that genuinely dangerous attributes
     * (onload, onerror, href=javascript: etc.) remain blocked.
     *
     * @return array<string, array<string, bool>>
     */
    public static function get_plugin_allowed_html(): array {
        static $map = null;
        if ( $map !== null ) {
            return $map;
        }

        // Attributes shared by almost every interactive element.
        $common = [
            'id'             => true,
            'class'          => true,
            'style'          => true,
            'title'          => true,
            'tabindex'       => true,
            'aria-label'     => true,
            'aria-labelledby'=> true,
            'aria-describedby'=> true,
            'aria-expanded'  => true,
            'aria-controls'  => true,
            'aria-selected'  => true,
            'aria-hidden'    => true,
            'aria-live'      => true,
            'aria-atomic'    => true,
            'aria-relevant'  => true,
            'role'           => true,
            'data-*'         => true,   // ALL data-* attributes preserved
        ];

        $map = [
            // ── Layout / structural ────────────────────────────────────────────
            'div'      => $common,
            'span'     => $common,
            'section'  => $common,
            'article'  => $common,
            'header'   => $common,
            'footer'   => $common,
            'main'     => $common,
            'aside'    => $common,
            'nav'      => $common,

            // ── Typography ────────────────────────────────────────────────────
            'p'        => $common,
            'h1'       => $common,
            'h2'       => $common,
            'h3'       => $common,
            'h4'       => $common,
            'h5'       => $common,
            'h6'       => $common,
            'strong'   => $common,
            'em'       => $common,
            'b'        => $common,
            'i'        => $common,
            'small'    => $common,
            'code'     => $common,
            'pre'      => $common,
            'br'       => [],
            'hr'       => $common,

            // ── Lists ─────────────────────────────────────────────────────────
            'ul'       => $common,
            'ol'       => $common,
            'li'       => $common,
            'dl'       => $common,
            'dt'       => $common,
            'dd'       => $common,

            // ── Links ─────────────────────────────────────────────────────────
            'a'        => array_merge( $common, [
                'href'   => true,
                'target' => true,
                'rel'    => true,
                'download'=> true,
            ] ),

            // ── Form container ────────────────────────────────────────────────
            'form'     => array_merge( $common, [
                'method'       => true,
                'action'       => true,
                'enctype'      => true,
                'autocomplete' => true,
                'novalidate'   => true,
            ] ),

            // ── Form fields ───────────────────────────────────────────────────
            'input'    => array_merge( $common, [
                'type'         => true,
                'name'         => true,
                'value'        => true,
                'placeholder'  => true,
                'required'     => true,
                'disabled'     => true,
                'readonly'     => true,
                'checked'      => true,
                'min'          => true,
                'max'          => true,
                'step'         => true,
                'maxlength'    => true,
                'minlength'    => true,
                'pattern'      => true,
                'autocomplete' => true,
                'size'         => true,
                'multiple'     => true,
                'accept'       => true,
            ] ),
            'textarea' => array_merge( $common, [
                'name'        => true,
                'rows'        => true,
                'cols'        => true,
                'placeholder' => true,
                'required'    => true,
                'disabled'    => true,
                'readonly'    => true,
                'maxlength'   => true,
                'wrap'        => true,
            ] ),
            'select'   => array_merge( $common, [
                'name'     => true,
                'required' => true,
                'disabled' => true,
                'multiple' => true,
                'size'     => true,
            ] ),
            'option'   => [
                'value'    => true,
                'selected' => true,
                'disabled' => true,
            ],
            'optgroup' => [
                'label'    => true,
                'disabled' => true,
            ],
            'label'    => array_merge( $common, [
                'for' => true,
            ] ),
            'fieldset' => $common,
            'legend'   => $common,

            // ── Buttons ───────────────────────────────────────────────────────
            'button'   => array_merge( $common, [
                'type'     => true,
                'name'     => true,
                'value'    => true,
                'disabled' => true,
                'form'     => true,
            ] ),

            // ── Media ─────────────────────────────────────────────────────────
            'img'      => array_merge( $common, [
                'src'     => true,
                'srcset'  => true,
                'sizes'   => true,
                'alt'     => true,
                'width'   => true,
                'height'  => true,
                'loading' => true,
                'decoding'=> true,
            ] ),
            'svg'      => array_merge( $common, [
                'xmlns'       => true,
                'viewbox'     => true,
                'fill'        => true,
                'stroke'      => true,
                'width'       => true,
                'height'      => true,
                'aria-hidden' => true,
            ] ),
            'canvas' => array_merge( $common, [
                'width'       => true,
                'height'      => true,
                'data-config' => true,
            ] ),
            'path'     => [
                'd'               => true,
                'fill'            => true,
                'stroke'          => true,
                'stroke-width'    => true,
                'stroke-linecap'  => true,
                'stroke-linejoin' => true,
            ],
            'circle'   => [ 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true, 'stroke' => true ],
            'rect'     => [ 'x' => true, 'y' => true, 'width' => true, 'height' => true, 'fill' => true, 'rx' => true ],
            'line'     => [ 'x1' => true, 'y1' => true, 'x2' => true, 'y2' => true, 'stroke' => true ],
            'polyline' => [ 'points' => true, 'fill' => true, 'stroke' => true ],
            'polygon'  => [ 'points' => true, 'fill' => true, 'stroke' => true ],
            'g'        => [ 'fill' => true, 'stroke' => true, 'transform' => true ],

            // ── Tables ────────────────────────────────────────────────────────
            'table'    => array_merge( $common, [ 'border' => true, 'cellpadding' => true, 'cellspacing' => true ] ),
            'thead'    => $common,
            'tbody'    => $common,
            'tfoot'    => $common,
            'tr'       => $common,
            'th'       => array_merge( $common, [ 'scope' => true, 'colspan' => true, 'rowspan' => true ] ),
            'td'       => array_merge( $common, [ 'colspan' => true, 'rowspan' => true ] ),
            'colgroup' => $common,
            'col'      => array_merge( $common, [ 'span' => true ] ),
            'caption'  => $common,

            // ── Miscellaneous ─────────────────────────────────────────────────
            'details'  => $common,
            'summary'  => $common,
            'progress' => array_merge( $common, [ 'value' => true, 'max' => true ] ),
            'meter'    => array_merge( $common, [ 'value' => true, 'min' => true, 'max' => true, 'low' => true, 'high' => true, 'optimum' => true ] ),
            'abbr'     => $common,
            'time'     => array_merge( $common, [ 'datetime' => true ] ),
            'mark'     => $common,
        ];

        return $map;
    }
}
