<?php
/**
 * Biscotti CMP Admin
 * 
 * Handles admin menu registration and dynamic view loading from the Biscotti Cloud API.
 * This is the Cloud-Bridge pattern: the plugin shell registers menus, but the actual
 * page content is fetched dynamically from the API based on the user's plan.
 * 
 * @package Biscotti_CMP
 * @since 3.0.0
 */

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

class Biscotti_Admin
{
    /**
     * @var Biscotti_API_Client
     */
    private $api;

    /**
     * Constructor
     * @param Biscotti_API_Client $api
     */
    public function __construct($api)
    {
        $this->api = $api;

        add_action('admin_menu', [$this, 'register_menus']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);

        // Suppress third-party admin notices on Biscotti pages
        add_action('in_admin_header', [$this, 'suppress_foreign_notices']);

        // AJAX handlers
        add_action('wp_ajax_biscotti_proxy_api', [$this, 'ajax_proxy_api']);
        add_action('wp_ajax_biscotti_proxy', [$this, 'ajax_proxy']);
        add_action('wp_ajax_biscotti_save_credentials', [$this, 'ajax_save_credentials']);
        add_action('wp_ajax_biscotti_clear_cache', [$this, 'ajax_clear_cache']);
        add_action('wp_ajax_biscotti_save_language', [$this, 'ajax_save_language']);
        add_action('wp_ajax_biscotti_disconnect', [$this, 'ajax_disconnect']);
        add_action('wp_ajax_biscotti_get_pages', [$this, 'ajax_get_pages']);
        add_action('wp_ajax_biscotti_assign_page', [$this, 'ajax_assign_page']);
        add_action('wp_ajax_biscotti_save_settings', [$this, 'ajax_save_settings']);
    }

    // =========================================================================
    // MENU REGISTRATION
    // =========================================================================

    public function register_menus()
    {
        // Main menu
        add_menu_page(
            __('Biscotti CMP', 'biscotti-cmp'),
            __('Biscotti CMP', 'biscotti-cmp'),
            'manage_options',
            'biscotti-cmp',
            [$this, 'render_page_dashboard'],
            'dashicons-shield-alt',
            80
        );

        // Submenu items — all use the dynamic view loader
        $menus = [
            ['biscotti-cmp',             __('Dashboard', 'biscotti-cmp'),           'render_page_dashboard'],
            ['biscotti-banner',          __('Banner Designer', 'biscotti-cmp'),     'render_page_banner'],
            ['biscotti-settings',        __('Settings', 'biscotti-cmp'),            'render_page_settings'],
            ['biscotti-subscription',    __('Subscription', 'biscotti-cmp'),        'render_page_subscription'],
            ['biscotti-compliance',      __('Compliance', 'biscotti-cmp'),          'render_page_compliance'],
            ['biscotti-legal-wizard',    __('Legal Wizard', 'biscotti-cmp'),        'render_page_legal_wizard'],
            ['biscotti-jurisdiction-targeting', __('Jurisdiction', 'biscotti-cmp'),  'render_page_jurisdiction_targeting'],
            ['biscotti-documents',       __('Documents', 'biscotti-cmp'),           'render_page_documents'],
            ['biscotti-services',        __('Services', 'biscotti-cmp'),            'render_page_services'],
            ['biscotti-data-processing', __('Data Processing', 'biscotti-cmp'),     'render_page_data_processing'],
            ['biscotti-white-label',     __('Branding', 'biscotti-cmp'),            'render_page_white_label'],
            ['biscotti-ab-testing',      __('A/B Testing', 'biscotti-cmp'),         'render_page_ab_testing'],
            ['biscotti-webhooks',        __('Webhooks', 'biscotti-cmp'),            'render_page_webhooks'],
            ['biscotti-audit-log',       __('Audit Log', 'biscotti-cmp'),           'render_page_audit_log'],
            ['biscotti-tools',           __('Tools', 'biscotti-cmp'),               'render_page_tools'],
            ['biscotti-about',           __('About', 'biscotti-cmp'),               'render_page_about'],
        ];

        foreach ($menus as $menu) {
            add_submenu_page(
                'biscotti-cmp',
                $menu[1],
                $menu[1],
                'manage_options',
                $menu[0],
                [$this, $menu[2]]
            );
        }

        // Hidden page (accessible via URL but not in menu)
        add_submenu_page(null, __('Express Setup', 'biscotti-cmp'), __('Express Setup', 'biscotti-cmp'), 'manage_options', 'biscotti-express-setup', [$this, 'render_page_express_setup']);
        add_submenu_page(null, __('Legal Suite', 'biscotti-cmp'), __('Legal Suite', 'biscotti-cmp'), 'manage_options', 'biscotti-legal-suite', [$this, 'render_page_legal_suite']);
    }

    // =========================================================================
    // ASSET ENQUEUE
    // =========================================================================

    public function enqueue_scripts($hook)
    {
        // Only load on Biscotti admin pages (C11 fix verified — this check correctly
        // prevents biscotti-admin CSS from loading on non-Biscotti pages like edit.php)
        if (strpos($hook, 'biscotti') === false) {
            return;
        }

        // Admin shell CSS
        wp_enqueue_style(
            'biscotti-admin',
            BISCOTTI_PLUGIN_URL . 'assets/css/admin.css',
            [],
            BISCOTTI_VERSION
        );

        // Foreign notice suppression is handled in admin.css (no inline styles needed)

        // Admin shell JS
        wp_enqueue_script(
            'biscotti-admin',
            BISCOTTI_PLUGIN_URL . 'assets/js/admin.js',
            ['jquery'],
            BISCOTTI_VERSION,
            true
        );

        wp_localize_script('biscotti-admin', 'biscottiAdmin', [
            'ajaxUrl'      => admin_url('admin-ajax.php'),
            'nonce'        => wp_create_nonce('biscotti_nonce'),
            'connected'    => $this->api->is_connected(),
            'websiteId'    => $this->api->get_website_id(),
            'dashboardUrl' => admin_url('admin.php?page=biscotti-cmp'),
            'i18n'         => [
                'connect'         => __('Connect', 'biscotti-cmp'),
                'saving'          => __('Saving...', 'biscotti-cmp'),
                'saved'           => __('Saved!', 'biscotti-cmp'),
                'error'           => __('Error', 'biscotti-cmp'),
                'connecting'      => __('Connecting...', 'biscotti-cmp'),
                'connected'       => __('Connected!', 'biscotti-cmp'),
                'connectionError' => __('Connection error', 'biscotti-cmp'),
                'loading'         => __('Loading...', 'biscotti-cmp'),
                'retry'           => __('Retry', 'biscotti-cmp'),
                'copied'          => __('Copied!', 'biscotti-cmp'),
                'confirmDelete'   => __('Are you sure you want to delete this item?', 'biscotti-cmp'),
                'deleted'         => __('Record deleted.', 'biscotti-cmp'),
                'scanning'        => __('Scanning...', 'biscotti-cmp'),
                'scanStarted'     => __('Scan started — analyzing your site...', 'biscotti-cmp'),
                'scanQueued'      => __('Scan queued. Refreshing...', 'biscotti-cmp'),
                'generating'      => __('Generating...', 'biscotti-cmp'),
                'selectType'      => __('Please select a document type.', 'biscotti-cmp'),
                'selectPage'      => __('Please select a page.', 'biscotti-cmp'),
                'noPages'         => __('No pages found.', 'biscotti-cmp'),
                'draft'           => __('Draft', 'biscotti-cmp'),
                'resetConfirm'    => __('Reset this provider to its detected defaults?', 'biscotti-cmp'),
                // A/B testing flows
                'confirmDeleteTest' => __('Delete this A/B test? This cannot be undone.', 'biscotti-cmp'),
                'confirmStart'      => __('Start this A/B test?', 'biscotti-cmp'),
                'confirmStop'       => __('Stop this A/B test?', 'biscotti-cmp'),
                'testNameRequired'  => __('Test name is required.', 'biscotti-cmp'),
                'twoVariants'       => __('At least 2 variants are required.', 'biscotti-cmp'),
                'maxVariants'       => __('Maximum 5 variants.', 'biscotti-cmp'),
                'variantName'       => __('Variant Name', 'biscotti-cmp'),
                'weight'            => __('Weight (%)', 'biscotti-cmp'),
                // A/B testing — per-variant editor labels/options for dynamically
                // added variants (must match the server-rendered ab-testing.php strings).
                'variant'           => __('Variant', 'biscotti-cmp'),
                'primaryColor'      => __('Primary Color', 'biscotti-cmp'),
                'borderRadius'      => __('Border Radius (px)', 'biscotti-cmp'),
                'position'          => __('Position', 'biscotti-cmp'),
                'posBottomCenter'   => __('Bottom Center', 'biscotti-cmp'),
                'posBottomLeft'     => __('Bottom Left', 'biscotti-cmp'),
                'posBottomRight'    => __('Bottom Right', 'biscotti-cmp'),
                'posTopCenter'      => __('Top Center', 'biscotti-cmp'),
                'posCenterModal'    => __('Center (Modal)', 'biscotti-cmp'),
                'variantType'       => __('Type', 'biscotti-cmp'),
                'typeBar'           => __('Bar', 'biscotti-cmp'),
                'typeModal'         => __('Modal', 'biscotti-cmp'),
                'typeCorner'        => __('Corner', 'biscotti-cmp'),
                'typeFloating'      => __('Floating', 'biscotti-cmp'),
                // Connection / settings flows
                'disconnectConfirm' => __('Disconnect this site from Biscotti?', 'biscotti-cmp'),
                'fillBothFields'    => __('Please fill in both fields.', 'biscotti-cmp'),
                'invalidWebsiteIdFormat' => __('Invalid Website ID format. Expected: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'biscotti-cmp'),
                // Jurisdiction / services flows
                'consentModel'      => __('Consent Model', 'biscotti-cmp'),
                'cookieBanner'      => __('Cookie Banner', 'biscotti-cmp'),
                'dpoRequired'       => __('DPO', 'biscotti-cmp'),
                'jurisdiction'      => __('Jurisdiction', 'biscotti-cmp'),
                'selectJurisdiction' => __('Please select a jurisdiction first.', 'biscotti-cmp'),
                'stateProvince'     => __('State / Province', 'biscotti-cmp'),
                'recommended'       => __('Recommended', 'biscotti-cmp'),
                'required'          => __('Required', 'biscotti-cmp'),
                'notRequired'       => __('Not required', 'biscotti-cmp'),
                // Generic action labels
                'generate'          => __('Generate', 'biscotti-cmp'),
                'regenerate'        => __('Regenerate', 'biscotti-cmp'),
                'remove'            => __('Remove', 'biscotti-cmp'),
                'select'            => __('Select', 'biscotti-cmp'),
                'next'              => __('Next', 'biscotti-cmp'),
                // Banner live-preview defaults
                'previewTitle'      => __('We value your privacy', 'biscotti-cmp'),
                'previewText'       => __('We use cookies to improve your experience.', 'biscotti-cmp'),
                'previewAccept'     => __('Accept All', 'biscotti-cmp'),
                'previewReject'     => __('Reject All', 'biscotti-cmp'),
                'previewSettings'   => __('Settings', 'biscotti-cmp'),
                'previewManage'     => __('Manage options', 'biscotti-cmp'),
                'previewTcfNotice'  => __('TCF 2.3 active — visitors also see the IAB purposes and the full vendor list; Accept and Reject get equal prominence and certain texts follow the IAB standard.', 'biscotti-cmp'),
            ],
        ]);

        // Connection wizard JS (loads on the connection prompt shown when not connected)
        if (!$this->api->is_connected()) {
            wp_enqueue_script(
                'biscotti-connection',
                BISCOTTI_PLUGIN_URL . 'assets/js/connection.js',
                ['jquery', 'biscotti-admin'],
                BISCOTTI_VERSION,
                true
            );
        }

        // Banner Designer page: WordPress native color picker + local designer script.
        // No inline/remote JS — the full designer logic lives in a local, human-readable
        // asset (assets/js/banner-designer.js), per WordPress.org Guideline 8.
        if ( strpos( $hook, 'biscotti-banner' ) !== false ) {
            wp_enqueue_style( 'wp-color-picker' );
            wp_enqueue_script( 'wp-color-picker' );
            wp_enqueue_script(
                'biscotti-banner-designer',
                BISCOTTI_PLUGIN_URL . 'assets/js/banner-designer.js',
                array( 'jquery', 'wp-color-picker', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // Settings page: local settings script (tabs + per-tab save flows).
        if ( strpos( $hook, 'biscotti-settings' ) !== false ) {
            wp_enqueue_script(
                'biscotti-settings',
                BISCOTTI_PLUGIN_URL . 'assets/js/settings.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // Tools / Scanner page: local scan-trigger script.
        if ( strpos( $hook, 'biscotti-tools' ) !== false ) {
            wp_enqueue_script(
                'biscotti-tools',
                BISCOTTI_PLUGIN_URL . 'assets/js/tools.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // Documents page: local documents management script (generate/edit/toggle/assign).
        if ( strpos( $hook, 'biscotti-documents' ) !== false ) {
            wp_enqueue_script(
                'biscotti-documents',
                BISCOTTI_PLUGIN_URL . 'assets/js/documents.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // Services / Provider Editor page: local provider editor script.
        if ( strpos( $hook, 'biscotti-services' ) !== false ) {
            wp_enqueue_script(
                'biscotti-services',
                BISCOTTI_PLUGIN_URL . 'assets/js/services.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // Jurisdiction & Targeting page.
        if ( strpos( $hook, 'biscotti-jurisdiction-targeting' ) !== false ) {
            wp_enqueue_script(
                'biscotti-jurisdiction-targeting',
                BISCOTTI_PLUGIN_URL . 'assets/js/jurisdiction-targeting.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // White Label page.
        if ( strpos( $hook, 'biscotti-white-label' ) !== false ) {
            wp_enqueue_script(
                'biscotti-white-label',
                BISCOTTI_PLUGIN_URL . 'assets/js/white-label.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // A/B Testing page.
        if ( strpos( $hook, 'biscotti-ab-testing' ) !== false ) {
            wp_enqueue_script(
                'biscotti-ab-testing',
                BISCOTTI_PLUGIN_URL . 'assets/js/ab-testing.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }

        // Legal Wizard page: local multi-step wizard script (navigation + save-legal-entity + generate-document).
        if ( strpos( $hook, 'biscotti-legal-wizard' ) !== false ) {
            wp_enqueue_script(
                'biscotti-legal-wizard',
                BISCOTTI_PLUGIN_URL . 'assets/js/legal-wizard.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
            // The Contract step renders the AGB questionnaire catalog inline, so the
            // shared questionnaire engine (conditional visibility + save-agb-data) must
            // load here too. Same script the old standalone Terms page used.
            wp_enqueue_script(
                'biscotti-agb-questionnaire',
                BISCOTTI_PLUGIN_URL . 'assets/js/agb-questionnaire.js',
                array( 'jquery', 'biscotti-admin' ),
                BISCOTTI_VERSION,
                true
            );
        }
    }

    // =========================================================================
    // PAGE RENDERERS — Dynamic View Loading
    // =========================================================================

    /**
     * Whitelist of valid page slugs for template inclusion.
     * Prevents directory traversal and arbitrary file inclusion.
     *
     * @var array
     */
    private static $valid_page_slugs = [
        'dashboard',
        'express-setup',
        'banner',
        'settings',
        'subscription',
        'legal-suite',
        'jurisdiction-targeting',
        'compliance',
        'legal-wizard',
        'data-processing',
        'white-label',
        'ab-testing',
        'webhooks',
        'audit-log',
        'tools',
        'about',
        'documents',
        'services',
        'shortcodes',
        'accessibility-scan',
    ];

    /**
     * Core method: loads a page template with data from the Biscotti Cloud API.
     * If not connected, shows the connection wizard instead.
     * If API returns an error, shows a graceful error notice.
     *
     * @param string $page_slug Page slug matching a template file in includes/templates/
     */
    private function render_dynamic_page($page_slug)
    {
        echo '<div class="wrap biscotti-admin-wrap">';

        // 1. Check connection
        if (!$this->api->is_connected()) {
            $this->render_connection_required();
            echo '</div>';
            return;
        }

        // 2. Validate page_slug against whitelist before any file inclusion
        if (!in_array($page_slug, self::$valid_page_slugs, true)) {
            $this->render_connection_error(__('Page template not found.', 'biscotti-cmp'));
            echo '</div>';
            return;
        }

        // 3. Fetch page data from Cloud API as JSON
        $data = $this->get_page_data($page_slug);

        // 4. Handle API errors gracefully
        if (is_wp_error($data)) {
            $this->render_connection_error($data->get_error_message());
            echo '</div>';
            return;
        }

        // 5. Include the local PHP template, passing $data in scope
        $template_file = BISCOTTI_PLUGIN_DIR . 'includes/templates/' . $page_slug . '.php';
        if (file_exists($template_file)) {
            include $template_file;
        } else {
            $this->render_connection_error(__('Page template not found.', 'biscotti-cmp'));
        }

        echo '</div>';
    }

    /**
     * Map page slugs to the appropriate API proxy action calls.
     * Returns the JSON data array for the template, or WP_Error on failure.
     *
     * @param string $page_slug Page slug
     * @return array|WP_Error
     */
    private function get_page_data($page_slug)
    {
        switch ($page_slug) {
            case 'dashboard':
                return $this->api->proxy_action('consent-stats');
            case 'banner':
                return $this->api->proxy_action('get-banner');
            case 'settings':
                return $this->api->proxy_action('get-settings');
            case 'subscription':
                return $this->api->proxy_action('get-subscription');
            case 'legal-wizard':
                // The Legal Wizard owns the whole flow. Fetch the wizard data AND
                // the AGB questionnaire catalog so the Contract step renders it
                // inline (mirrors the Cloud app, where the Contract step IS the AGB
                // questionnaire). A catalog-fetch failure must NOT break the wizard —
                // degrade gracefully without it.
                $lw_data = $this->api->proxy_action('get-legal-wizard-data');
                if (is_wp_error($lw_data)) {
                    return $lw_data;
                }
                if (!is_array($lw_data)) {
                    $lw_data = [];
                }
                $agb_catalog = $this->api->proxy_action('get-agb-catalog');
                if (!is_wp_error($agb_catalog) && is_array($agb_catalog)) {
                    $lw_data['agb'] = $agb_catalog;
                }
                return $lw_data;
            case 'jurisdiction-targeting':
                return $this->api->proxy_action('get-legal-wizard-data');
            case 'legal-suite':
                return $this->api->proxy_action('get-legal-wizard-data');
            case 'documents':
                return $this->api->proxy_action('get-documents');
            case 'tools':
                return $this->api->proxy_action('get-scan-results');
            case 'compliance':
                return $this->api->proxy_action('get-compliance-report');
            case 'services':
                return $this->api->proxy_action('get-services');
            case 'data-processing':
                return $this->api->proxy_action('get-data-processing-records');
            case 'accessibility-scan':
                return $this->api->proxy_action('get-accessibility-results');
            // About page uses heartbeat for account/plan info
            case 'about':
                return $this->api->heartbeat();
            case 'ab-testing':
                return $this->api->proxy_action('get-ab-tests');
            // Static pages need no API data
            case 'shortcodes':
            case 'webhooks':
            case 'audit-log':
            case 'white-label':
                return [];
            default:
                return [];
        }
    }

    /**
     * Render connection required screen
     */
    private function render_connection_required()
    {
        ?>
        <div class="biscotti-connection-wizard">
            <div class="biscotti-connection-card">
                <img src="<?php echo esc_url(BISCOTTI_PLUGIN_URL . 'assets/biscotti-logo.png'); ?>"
                     alt="Biscotti CMP" class="biscotti-logo" width="120">
                <h2><?php esc_html_e('Connect to Biscotti CMP', 'biscotti-cmp'); ?></h2>
                <p><?php esc_html_e('Enter your Website ID to connect this WordPress site to your Biscotti CMP account.', 'biscotti-cmp'); ?></p>

                <div class="biscotti-form-group">
                    <label for="biscotti-website-id"><?php esc_html_e('Website ID', 'biscotti-cmp'); ?></label>
                    <input type="text"
                           id="biscotti-website-id"
                           name="biscotti_website_id"
                           class="regular-text"
                           placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                           value="">
                </div>

                <button type="button" id="biscotti-connect-btn" class="button button-primary button-hero">
                    <?php esc_html_e('Connect', 'biscotti-cmp'); ?>
                </button>

                <p class="biscotti-help-text">
                    <?php
                    printf(
                        /* translators: %s: Dashboard URL */
                        esc_html__('Find your Website ID in the %s.', 'biscotti-cmp'),
                        '<a href="https://app.biscotti-cmp.com" target="_blank">' . esc_html__('Biscotti Dashboard', 'biscotti-cmp') . '</a>'
                    );
                    ?>
                </p>

                <div id="biscotti-connection-status"></div>
            </div>
        </div>
        <?php
    }


    /**
     * Render connection error
     * @param string $message
     */
    private function render_connection_error($message = '')
    {
        ?>
        <div class="notice notice-error">
            <p>
                <strong><?php esc_html_e('Connection Error', 'biscotti-cmp'); ?></strong>:
                <?php echo esc_html($message ?: __('Could not connect to the Biscotti CMP API. Please try again later.', 'biscotti-cmp')); ?>
            </p>
        </div>
        <?php
    }

    // =========================================================================
    // INDIVIDUAL PAGE RENDER METHODS (all delegate to render_dynamic_page)
    // =========================================================================

    public function render_page_dashboard()        { $this->render_dynamic_page('dashboard'); }
    public function render_page_express_setup()    { $this->render_dynamic_page('express-setup'); }
    public function render_page_banner()           { $this->render_dynamic_page('banner'); }
    public function render_page_settings()         { $this->render_dynamic_page('settings'); }
    public function render_page_subscription()     { $this->render_dynamic_page('subscription'); }
    public function render_page_legal_suite()      { $this->render_dynamic_page('legal-suite'); }
    public function render_page_compliance()       { $this->render_dynamic_page('compliance'); }
    public function render_page_legal_wizard()    { $this->render_dynamic_page('legal-wizard'); }
    public function render_page_data_processing()  { $this->render_dynamic_page('data-processing'); }
    public function render_page_white_label()      { $this->render_dynamic_page('white-label'); }
    public function render_page_ab_testing()       { $this->render_dynamic_page('ab-testing'); }
    public function render_page_webhooks()         { $this->render_dynamic_page('webhooks'); }
    public function render_page_audit_log()        { $this->render_dynamic_page('audit-log'); }
    public function render_page_tools()            { $this->render_dynamic_page('tools'); }
    public function render_page_about()            { $this->render_dynamic_page('about'); }
    public function render_page_documents()        { $this->render_dynamic_page('documents'); }
    public function render_page_services()         { $this->render_dynamic_page('services'); }
    public function render_page_jurisdiction_targeting() { $this->render_dynamic_page('jurisdiction-targeting'); }
    public function render_page_accessibility_scan() { $this->render_dynamic_page('accessibility-scan'); }

    // =========================================================================
    // SUPPRESS THIRD-PARTY ADMIN NOTICES ON BISCOTTI PAGES
    // =========================================================================

    /**
     * Suppress third-party admin notices on Biscotti CMP pages.
     * Removes both 'all_admin_notices' and 'admin_notices' to prevent
     * other plugins from injecting their notices into Biscotti screens.
     * Also outputs CSS as a fallback to hide any notices that slip through.
     */
    public function suppress_foreign_notices()
    {
        $screen = get_current_screen();
        if (!$screen || strpos($screen->id, 'biscotti') === false) {
            return;
        }
        // Remove all third-party notice hooks
        remove_all_actions('all_admin_notices');
        remove_all_actions('admin_notices');
    }

    // =========================================================================
    // AJAX HANDLERS
    // =========================================================================

    /**
     * Generic API proxy AJAX handler.
     * WordPress JS sends AJAX calls here, which are then forwarded to the Biscotti API.
     * Only allowlisted endpoint prefixes are permitted.
     */
    public function ajax_proxy_api()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $endpoint = isset($_POST['endpoint']) ? sanitize_text_field(wp_unslash($_POST['endpoint'])) : '';
        $method   = isset($_POST['method']) ? sanitize_text_field(wp_unslash($_POST['method'])) : 'GET';
        $params   = isset($_POST['params']) ? sanitize_text_field(wp_unslash($_POST['params'])) : '';

        if (empty($endpoint)) {
            wp_send_json_error(__('Endpoint required', 'biscotti-cmp'));
        }

        // Allowlist of permitted API endpoint prefixes
        $allowed_prefixes = [
            '/wp-plugin/',
            '/consent/',
            '/legal/',
            '/config/',
            '/banners/',
            '/websites/',
            '/scans/',
            '/tcf/',
        ];

        $is_allowed = false;
        foreach ($allowed_prefixes as $prefix) {
            if (strpos($endpoint, $prefix) === 0) {
                $is_allowed = true;
                break;
            }
        }

        if (!$is_allowed) {
            wp_send_json_error(__('Endpoint not permitted', 'biscotti-cmp'));
        }

        // Decode JSON params if provided
        $body = null;
        if (!empty($params)) {
            $body = json_decode($params, true);
        }

        $result = $this->api->proxy_raw($method, '/api/v1' . $endpoint, $body);

        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        }

        wp_send_json_success($result);
    }

    /**
     * Simplified proxy AJAX handler for wp-plugin proxy actions.
     * JS sends: action=biscotti_proxy, nonce, proxy_action, payload (JSON string)
     * This calls Biscotti_API_Client::proxy_action() which POSTs to /wp-plugin/proxy/{action}
     */
    public function ajax_proxy()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $action_name = isset($_POST['proxy_action']) ? sanitize_text_field(wp_unslash($_POST['proxy_action'])) : '';

        if (empty($action_name)) {
            wp_send_json_error(__('Action name required', 'biscotti-cmp'));
        }

        // Allowlist of permitted proxy actions
        $allowed_actions = [
            'consent-stats',
            'create-ab-test',
            'create-data-processing-record',
            'delete-ab-test',
            'delete-data-processing-record',
            'delete-provider',
            'generate-document',
            'get-accessibility-results',
            'get-ab-tests',
            'get-agb-catalog',
            'get-banner',
            'get-compliance-report',
            'get-data-processing-records',
            'get-documents',
            'get-jurisdiction-questions',
            'get-jurisdiction-schema',
            'get-legal-entity',
            'get-legal-wizard-data',
            'get-provider',
            'get-scan-results',
            'get-services',
            'get-settings',
            'get-subscription',
            'save-banner',
            'save-branding',
            'save-agb-data',
            'save-document-content',
            'save-legal-entity',
            'save-provider',
            'save-settings',
            'start-ab-test',
            'stop-ab-test',
            'toggle-document',
            'trigger-scan',
            'update-ab-test',
            'view-document',
        ];

        if (!in_array($action_name, $allowed_actions, true)) {
            wp_send_json_error(__('Action not permitted', 'biscotti-cmp'));
        }

        // Decode JSON payload
        $payload = [];
        if (!empty($_POST['payload'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded and passed to API
            $raw_payload = wp_unslash($_POST['payload']);
            $payload = json_decode($raw_payload, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                wp_send_json_error(__('Invalid JSON payload', 'biscotti-cmp'));
            }
        }

        $result = $this->api->proxy_action($action_name, $payload);

        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        }

        // Sanitize document content with wp_kses_post before sending to client.
        if ( 'view-document' === $action_name && is_array( $result ) ) {
            if ( isset( $result['content'] ) ) {
                $result['content'] = wp_kses_post( $result['content'] );
            }
            if ( isset( $result['document']['content'] ) ) {
                $result['document']['content'] = wp_kses_post( $result['document']['content'] );
            }
        }

        wp_send_json_success($result);
    }

    /**
     * Save connection credentials (Website ID + optional API key)
     */
    public function ajax_save_credentials()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $website_id = isset($_POST['website_id']) ? sanitize_text_field(wp_unslash($_POST['website_id'])) : '';
        $api_key    = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';

        if (empty($website_id)) {
            wp_send_json_error(__('Website ID is required', 'biscotti-cmp'));
        }

        // Validate UUID format
        if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $website_id)) {
            wp_send_json_error(__('Invalid Website ID format', 'biscotti-cmp'));
        }

        // Save credentials
        $options = get_option('biscotti_options', []);
        $options['website_id'] = $website_id;
        $options['api_key'] = $api_key;
        $options['banner_enabled'] = true;
        update_option('biscotti_options', $options);

        // Verify connection with heartbeat
        $client = new Biscotti_API_Client();
        $heartbeat = $client->heartbeat();

        if (is_wp_error($heartbeat)) {
            // Still save the ID but warn about connection
            wp_send_json_success([
                'connected' => false,
                'message'   => __('Credentials saved but connection could not be verified.', 'biscotti-cmp'),
            ]);
        }

        // Clear cache
        $client->clear_cache();

        wp_send_json_success([
            'connected' => true,
            'plan'      => $heartbeat['plan'] ?? 'free',
            'domain'    => $heartbeat['domain'] ?? '',
            'message'   => __('Successfully connected!', 'biscotti-cmp'),
        ]);
    }

    /**
     * Clear API response cache
     */
    public function ajax_clear_cache()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $this->api->clear_cache();
        wp_send_json_success(__('Cache cleared', 'biscotti-cmp'));
    }

    /**
     * Get WordPress Pages for the Document Assignment dropdown
     */
    public function ajax_get_pages()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $pages = get_pages(['post_status' => 'publish,draft']);
        $formatted = [];
        foreach ($pages as $p) {
            $formatted[] = [
                'id' => $p->ID,
                'title' => $p->post_title,
                'status' => $p->post_status
            ];
        }
        
        wp_send_json_success($formatted);
    }

    /**
     * Assign a document shortcode to a specific WordPress page
     */
    public function ajax_assign_page()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $page_id = isset($_POST['page_id']) ? intval($_POST['page_id']) : 0;
        $doc_type = isset($_POST['document_type']) ? sanitize_text_field(wp_unslash($_POST['document_type'])) : '';

        if (!$page_id || empty($doc_type)) {
            wp_send_json_error(__('Missing parameters', 'biscotti-cmp'));
        }

        $shortcode = sprintf('[biscotti-document type="%s"]', strtolower($doc_type));
        $post = get_post($page_id);
        
        if (!$post || $post->post_type !== 'page') {
            wp_send_json_error(__('Invalid page', 'biscotti-cmp'));
        }

        // Append shortcode to existing content instead of replacing it.
        // This preserves any existing page content the user has created.
        $existing_content = $post->post_content ?? '';
        
        // Don't add duplicate shortcodes
        if (strpos($existing_content, $shortcode) !== false) {
            wp_send_json_success(__('Shortcode already present on this page', 'biscotti-cmp'));
            return;
        }

        $new_content = trim($existing_content) !== '' 
            ? $existing_content . "\n\n" . $shortcode 
            : $shortcode;

        wp_update_post([
            'ID' => $page_id,
            'post_content' => $new_content
        ]);

        wp_send_json_success(__('Page assigned successfully', 'biscotti-cmp'));
    }

    /**
     * Save plugin language preference
     */
    public function ajax_save_language()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $language = isset($_POST['language']) ? sanitize_text_field(wp_unslash($_POST['language'])) : '';

        // Accept ISO language codes with an optional region suffix (e.g. de, en, pt-BR, pt-PT, zh-TW).
        if (empty($language) || !preg_match('/^[a-z]{2}(-[A-Za-z]{2})?$/', $language)) {
            wp_send_json_error(__('Invalid language code', 'biscotti-cmp'));
        }

        $options = get_option('biscotti_options', []);
        $options['default_language'] = $language;
        update_option('biscotti_options', $options);

        // Clear cache so views re-render in the new language
        $this->api->clear_cache();

        wp_send_json_success([
            'language' => $language,
            'message'  => __('Language saved', 'biscotti-cmp'),
        ]);
    }

    /**
     * Disconnect website — clear credentials
     */
    public function ajax_disconnect()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $options = get_option('biscotti_options', []);
        $options['website_id'] = '';
        $options['api_key'] = '';
        $options['banner_enabled'] = false;
        update_option('biscotti_options', $options);

        // Clear cache
        $this->api->clear_cache();

        wp_send_json_success([
            'message' => __('Website disconnected', 'biscotti-cmp'),
        ]);
    }

    /**
     * Save local plugin settings that affect frontend rendering (C5 fix).
     * Called by the settings view JS after successful API save.
     * Persists fields to biscotti_options so class-banner.php can read them
     * for BiscottiConfig emission without depending on the API being reachable.
     */
    public function ajax_save_settings()
    {
        check_ajax_referer('biscotti_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'biscotti-cmp'));
        }

        $options = get_option('biscotti_options', []);

        // Fields that class-banner.php reads for BiscottiConfig
        $fields = [
            'banner_enabled'       => 'boolval',
            'primary_color'        => 'sanitize_hex_color',
            'show_attribution'     => 'boolval',
            'show_floating_button' => 'boolval',
            'tcf_enabled'          => 'boolval',
            'google_consent_mode'  => 'boolval',
            'auto_block'           => 'boolval',
            'consent_duration'     => 'intval',
            'banner_position'      => 'sanitize_text_field',
            'default_language'     => 'sanitize_text_field',
        ];

        foreach ($fields as $field => $sanitizer) {
            if (isset($_POST[$field])) {
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized per-type below
                $raw = sanitize_text_field(wp_unslash($_POST[$field]));
                if ($sanitizer === 'boolval') {
                    $options[$field] = filter_var($raw, FILTER_VALIDATE_BOOLEAN);
                } elseif ($sanitizer === 'intval') {
                    $options[$field] = intval($raw);
                } elseif ($sanitizer === 'sanitize_hex_color') {
                    $sanitized = sanitize_hex_color($raw);
                    $options[$field] = $sanitized ?: ($options[$field] ?? '');
                } else {
                    $options[$field] = $raw;
                }
            }
        }

        update_option('biscotti_options', $options);

        // Clear view cache so next page load reflects new settings
        $this->api->clear_cache();

        wp_send_json_success(['message' => __('Settings saved', 'biscotti-cmp')]);
    }
}
